zoukankan      html  css  js  c++  java
  • 【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
    ",f[n]);
        }
        return 0;
    }
  • 相关阅读:
    jQuery 请指出'$'和'$.fn'的区别?或者说出'$.fn'的用途。
    ie8及其以下浏览器的document.getElementsByClassName兼容性问题
    document.all的详细解释(document.all基本上所有浏览器可用!)
    CSS浮动属性Float介绍
    JCarouselLite--帮助文档
    css定位之z-index问题分析
    Android手机同步电脑端google chrome书签
    AWK原理及命令和文件输入
    Sed命令
    Shell正则表达式
  • 原文地址:https://www.cnblogs.com/2016gdgzoi471/p/9476889.html
Copyright © 2011-2022 走看看