斜率优化DP(HYSBZ - 1010 +HDU 3507)

思路:这类问题有一个特点,n特别大 1e4以上,而且转移方程必须O(n^2)才能跑完。

通过化简式子可以找到优化的途径。

例如HYSBZ-1010

我们可以写出转移方程dp[i]=min(dp[j]+(sum[i]-sum[j]+i-j-1-L)^2) (i>j>=0)

假设k<j<i,且 j比k更优, 这时候会得到表达式

dp[j]+(sum[i]-sum[j]+i-j-1-L)^2>=dp[k]+(sum[i]-sum[k]+i-j-1-L)^2

t=(i-1-L),这时候会化简成

sum[i]>={(dp[j]-2*(i-1-M)*a+a*a-2*(i-1-L)*sum[j]+2*j*sum[j]+sum[j]*sum[j])-(dp[j]-2*(i-1-M)*j+j*j-2*(i-1-M)*sum[k]+2*k*sum[k]+sum[k]*sum[k])}/{2*(j+sum[j]-k-sum[k])}

也就是最终转化成左边只有sum[i]的式子,这就相当于j,k两点的斜率,我们用单调队列维护一个斜率递增的j, k

对于top++, rear--操作:

当队列头的两个元素斜率小于sum【i】, 说明还能优化,所以top++,取更大的斜率

对于rear--是要维护一个下凸包。

HYSBZ - 1010

#include<cstdio>
#include<iostream>
#include<cstring>
#define MAXN 500000+10
#define LARGE 23333333
using namespace std;
typedef long long LL;
int n, top, rear;
LL dp[MAXN], M, sum[MAXN], S[MAXN];

LL GetUp(int a, int b, int i)
{
    return (dp[a]-2*(i-1-M)*a+a*a-2*(i-1-M)*sum[a]+2*a*sum[a]+sum[a]*sum[a])-
           (dp[b]-2*(i-1-M)*b+b*b-2*(i-1-M)*sum[b]+2*b*sum[b]+sum[b]*sum[b]);
}

LL GetDown(int a, int b)
{
    return 2*(a+sum[a]-b-sum[b]);
}

int main()
{
    scanf("%d%lld", &n, &M);
    LL tmp;
    sum[0]=dp[0]=0;
    for(int i=1; i<=n; i++)
    {
        scanf("%lld", &tmp);
        sum[i]=sum[i-1]+tmp;
    }
    top=rear=0;
    S[rear++]=0;

    for(int i=1; i<=n; i++)
    {

        while(top+1<rear && GetUp(S[top+1], S[top], i)<=GetDown(S[top+1], S[top])*sum[i]) top++;
        int j=S[top];
        dp[i]=dp[j]+(i-j-1+sum[i]-sum[j]-M)*(i-j-1+sum[i]-sum[j]-M);
        while(top+1<rear &&
                GetUp(i, S[rear-1], i)*GetDown(S[rear-1], S[rear-2])<=GetUp(S[rear-1], S[rear-2], i)*GetDown(i, S[rear-1]))rear--;
        S[rear++]=i;
    }
    printf("%lld\n", dp[n]);

    return 0;
}

HDU 3507

#include<cstdio>
#include<iostream>
#include<cstring>
#define MAXN 500000+10
#define LL long long
#define LARGE 23333333
using namespace std;

int n, top, rear;
LL dp[MAXN], M, sum[MAXN], S[MAXN];

LL GetUp(int a, int b){
    return (dp[a]+sum[a]*sum[a])-(dp[b]+sum[b]*sum[b]);
}

LL GetDown(int a, int b){
    return 2*(sum[a]-sum[b]);
}

int main()
{
   while(~scanf("%d%lld", &n, &M)){
        LL tmp;
        sum[0]=dp[0]=0;
        for(int i=1; i<=n; i++){
            scanf("%lld", &tmp);
            sum[i]=sum[i-1]+tmp;
        }
        top=rear=0;
        S[rear++]=0;

        for(int i=1; i<=n; i++){

            while(top+1<rear && GetUp(S[top+1], S[top])<=GetDown(S[top+1], S[top])*sum[i]) top++;
            int j=S[top];
            dp[i]=dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+M;
            while(top+1<rear &&
                  GetUp(i, S[rear-1])*GetDown(S[rear-1], S[rear-2])<=GetUp(S[rear-1], S[rear-2])*GetDown(i, S[rear-1]))rear--;
            S[rear++]=i;
        }
        printf("%lld\n", dp[n]);
   }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/du_lun/article/details/81327411