bzoj4518 [ SDOI2016 ] --斜率优化DP

设x[i]为第i天走的路程,s为路程总和,则:

ans=[(s/m-x[1])^2+(s/m-x[2])^2+(s/m-x[3])^2+...+(s/m-x[m])^2]*m

     =[(s-x[1]*m)^2+(s-x[2]*m)^2+(s-x[3]*m)^2]+...+(s-x[m]*m)^2)]/m

     =s^2+m*(x[1]^2+x[2]^2+x[3]^2+...+x[m]^2)-2*(x[1]+x[2]+x[3]+...+x[m])*s

     =m*(x[1]^2+x[2]^2+x[3]^2+...+x[m]^2)-s^2

由于m,s不变,问题可以转化为将一个长为n的序列划分成m段,求最小平方和

令f[i][j]为将长为j的序列划分成i段的最小平方和,则

f[i][j]=min(f[i][k]+(sum[j]-sum[k])^2),0<k<j

很明显的斜率优化。

 

斜率优化过程如下:

设p<k<j,在求f[i][j]时k比p优,于是可得如下方程:
f[i-1][k]+(sum[j]-sum[k])^2<f[i-1][p]+(sum[j]-sum[p])^2

f[i-1][k]+sum[k]^2-(f[i-1][p]+sum[p]^2)<2*(sum[k]-sum[p])*sum[j]

(f[i-1][k]+sum[k]^2-(f[i-1][p]+sum[p]^2))/(2*sum[k]-2*sum[p])<sum[j]

如果令y[i-1][t]=f[i-1][t]+sum[t]^2,x[i-1][t]=2*sum[t],

则原式可化为:(y[i-1][k]-y[i-1][p])/(x[i-1][k]-x[i-1][p])<sum[j],不就是线段kp的斜率小于sum[j]吗?

令g[i][j](i>j)为线段ij的斜率。

于是在求f[i]时维护一个序列,满足对任意j(j>2),g[j][j-1]>g[j-1][j-2],则f[i][j]的最优值就从序列中斜率小于sum[j]的序号最大的线段的右端点转移。

时间复杂度从O(n*n*m)降到O(n*m)

 

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
ll i,j,k,n,m,f[3001][3001],x[3001],y[3001][3001],sum[3001],s,l,r,q[3001];
int main()
{
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%lld",&sum[i]),s+=sum[i],sum[i]+=sum[i-1],x[i]=sum[i]<<1,f[1][i]=sum[i]*sum[i],y[1][i]=f[1][i]<<1;
    for(i=2;i<=m;i++){
        l=r=0;
        for(j=1;j<=n;j++){
            while(l<r&&y[i-1][q[l+1]]-y[i-1][q[l]]<=sum[j]*(x[q[l+1]]-x[q[l]]))l++;
            f[i][j]=f[i-1][q[l]]+(sum[j]-sum[q[l]])*(sum[j]-sum[q[l]]);
            y[i][j]=f[i][j]+sum[j]*sum[j];
            while(l<r&&(x[q[r]]-x[q[r-1]])*(y[i-1][j]-y[i-1][q[r-1]])<=(x[j]-x[q[r-1]])*(y[i-1][q[r]]-y[i-1][q[r-1]]))r--;
            q[++r]=j;
        }
    }
    printf("%lld\n",f[m][n]*m-s*s);
    return 0;
}
bzoj4518

 

posted @ 2016-10-25 19:34  gjghfd  阅读(377)  评论(0编辑  收藏  举报