bzoj3675 [APIO2014] 序列分割(斜率优化)

2d/1d的转移方程的斜率优化。
首先我们根据这个分割的过程可以发现:总得分等于k+1段两两的乘积的和(乘法分配律),也就是说与分割顺序是无关的。再对乘积进行重分组(还是乘法分配律)我们可以转化为:ans=∑第 i 段×前 i-1 段的和,那我们可以认为这k+1段是从左到右依次分出来的。假设目前这段是j+1…i,则他对答案的贡献为(sum[i]-sum[j])*sum[j].这样就很容易可以得到 O(kn2) 的状态转移方程
f[i][k]=max{f[j][k1]+(sum[i]sum[j])sum[j]|k<=j<i}
前i个数砍k刀的最大收益,枚举第k刀砍在j后面。进行斜率优化时要小心有0的存在,因此我们在读入时就把0都去掉,在n==k时及时退出,就不会影响答案。
假设k1< k2且k1优于k2,则有(f[k-1][k1]-f[k-1][k2]+sum[k2]*sum[k2]-sum[k1]*sum[k1])/(sum[k2]-sum[k1])>sum[i]。所以我们维护一个下凸曲线,保证队列中的斜率单增。用滚动数组优化空间复杂度为 O(n) ,时间复杂度为 O(nk)

#include 
#include 
#define N 100010
#define ll long long
int a[N],n,k,q[N],h=0,t=1,p=0;
ll sum[N],f[2][N];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline double slope(int k1,int k2){
    return (f[p][k1]-f[p][k2]+sum[k2]*sum[k2]-sum[k1]*sum[k1])*1.0/(sum[k2]-sum[k1]);
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();k=read();
    for(int i=1;i<=n;++i){a[i]=read();if(a[i]==0) --i,--n;}
    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
    for(int x=1;x<=k;++x){
        memset(q,0,sizeof(q));h=0,t=1;q[++h]=x;
        for(int i=x+1;i<=n;++i){
            while(hq[h],q[h+1])1][i]=f[p][q[h]]+(sum[i]-sum[q[h]])*sum[q[h]];
            while(hq[t],i)q[t-1],q[t])) --t;
            q[++t]=i;
        }p^=1;
    }
    printf("%lld\n",f[p][n]);
    return 0;
}

你可能感兴趣的:(bzoj,区间dp,决策单调性)