【poj3709】K-Anonymous Sequence 【斜率优化dp】

题目链接
题意:把一个递增数列分成若干组,每组至少k个,每组的花费是这组的数字和减去最小值乘这组的总个数。求最小总花费。
首先,我们想一个朴素的dp方程。把这个序列翻转过来, f[i]表示前i个数的最小花费,方程为:
f[i]=min(f[j]+sum[i]sum[j](ij)a[i])(ijk)f[i]=min(f[j]+sum[i]−sum[j]−(i−j)∗a[i])(i−j≥k)
sum是前缀和,a代表每个数字。
朴素的时间复杂度是O(n^2)的。我们发现可以对其使用斜率优化。
决策单调性证明:
设现在要求i的答案,并且k是j后面的一个决策(即k>j),且k的答案不比j差。
把dp方程展开后得
f[k]+sum[i]sum[k]ia[i]+ka[i]f[j]+sum[i]sum[j]ia[i]+ja[i]f[k]+sum[i]−sum[k]−i∗a[i]+k∗a[i]≤f[j]+sum[i]−sum[j]−i∗a[i]+j∗a[i]
整理得
f[k]sum[k]+ka[i]f[j]sum[j]+ja[i]f[k]−sum[k]+k∗a[i]≤f[j]−sum[j]+j∗a[i]
设i后面有一个决策l,x=a[l]a[i]x=a[l]−a[i]
则我们要证明f[k]sum[k]+ka[i]kx<=f[j]sum[j]+ja[i]jxf[k]−sum[k]+k∗a[i]−k∗x<=f[j]−sum[j]+j∗a[i]−j∗x
因为f[k]sum[k]+ka[i]f[j]sum[j]+ja[i]f[k]−sum[k]+k∗a[i]≤f[j]−sum[j]+j∗a[i],且k>jk>j,x相同,
所以上面那个成立,这个dp方程具有决策单调性。
因此对于任何k>j且k对i的答案不差于j对i的答案的两个决策,k对任何l>i的答案都不差于j对l的答案。
斜率优化:
刚才的式子:f[k]sum[k]+ka[i]f[j]sum[j]+ja[i]f[k]−sum[k]+k∗a[i]≤f[j]−sum[j]+j∗a[i]
移项得:f[k]sum[k]f[j]+sum[j](jk)a[i]f[k]−sum[k]−f[j]+sum[j]≤(j−k)∗a[i]
由于k < j,所以j-k是负数,除过来时要注意变号。
把两边同时除以(k-j)得

f[k]sum[k]f[j]+sum[j]kja[i]f[k]−sum[k]−f[j]+sum[j]k−j≤−a[i]

这就是我们要的斜率公式。
于是就可以愉快地进行斜率优化dp了!
由于离i最近的k个数不是合法决策,所以进队要有一个时间差。
出队的判断条件:
队列本质上是维护一些单调增加的斜率,也就是维护一个下凸壳。
1. 当slope(q[head],q[head+1])<=a[i]slope(q[head],q[head+1])<=−a[i]时,q[head]比q[head+1]要差,已经不是最优解了,所以把队头出队。
2. 当slope(q[tail],im+1)<slope(q[tail1],q[tail])slope(q[tail],i−m+1)<slope(q[tail−1],q[tail])时,i-m+1比q[tail]要优,所以就把q[tail]出队了。
细节详见代码。
去网上膜了一发题解,发现貌似只有我一个蒟蒻是把数列倒过来乱搞的,公式好像不大一样。
简洁明了优美的 代码:<===你看,博主又不要脸了 = =
#include<cstdio>
#define int long long
const int N=500005;
int kase,n,m,head,tail,a[N],sum[N],f[N],q[N];
double slope(int j,int k){
    return (1.0*f[k]-sum[k]-f[j]+sum[j])/(k-j);
}
signed main(){
    scanf("%lld",&kase);
    while(kase--){
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[n-i+1]);
        }
        for(int i=1;i<=n;i++){
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=m;i<2*m&&i<=n;i++){
            f[i]=sum[i]-i*a[i];
        }
        head=1,tail=0;
        q[++tail]=m;
        for(int i=2*m;i<=n;i++){
            while(head<tail&&slope(q[head],q[head+1])<=-a[i]){
                head++;
            }
            f[i]=f[q[head]]+sum[i]-sum[q[head]]-(i-q[head])*a[i];
            while(head<tail&&slope(q[tail],i-m+1)<slope(q[tail-1],q[tail])){
                tail--;
            }
            q[++tail]=i-m+1;
        }
        printf("%lld\n",f[n]);
    }
    return 0;
}
posted @ 2018-03-24 22:02  一剑霜寒十四洲  阅读(118)  评论(0编辑  收藏  举报