• BZOJ4518: [Sdoi2016]征途


    【传送门:BZOJ4518


    简要题意:

      给出n个数,要求分成m个段,使得这些段的数和的方差最小,并将方差*m^2


    题解:

      斜率优化DP

      我们设ans为最后的答案,sum表示所有的数之和,c[i]表示最优解中第i段数字和

      下面式子中,1<=i<=m

      ans=[∑(c[i]-sum/m)^2]/m*m^2
        =[∑(c[i]-sum/m)^2]*m
        =[∑(c[i]^2-2*c[i]*sum/m+sum^2/m^2)]*m
        =∑(c[i]^2*m-2*c[i]*sum+sum^2/m)
        =∑(c[i]^2*m-2*c[i]*sum)+sum^2

      因为sum为所有数字和,所以sum=c[1]+...+c[m]

        =∑(c[i]^2*m)+sum^2-2*sum^2
        =m*∑(c[i]^2)-sum^2

      因为m和sum^2的值是确定的,所以我们要求的是最小的每个段的平方和(也就是∑(c[i]^2))

      设f数组,f[i][k]表示前i个数分成k个段的最小平方和
      设s数组,s[i]表示1到i的数的和

      那么我们很容易得到这样的方程:(1<=i<=n)

      f[i][k]=min(f[j][k-1]+(s[i]-s[j])^2)

      但是这样做的时间复杂度为O(n^2*m),绝对T

      那么我们就用斜率优化来优化
      以前都是一维的DP斜率优化,现在二维应该怎么做呢?(我一开始也不会。。)

      从上面的DP方程,我们发现其实每次询问的时候都是只询问了分成当前减1的段的最优解(也就是f[i][k]询问f[j][k-1])

      所以我们定义f1数组表示现在要分成k段要求的最小平方和,f2数组表示之前已经处理好的分成k-1段的最小平方和

      这样我们就相当于用滚动的方法来处理,这样就能得到:

      f1[i]=min(f2[j]+(s[i]-s[j])^2)

      设j1<j2<i

      就可以得到斜率不等式(f2[j2]-f2[j1]+s[j2]^2-s[j1]^2)/(s[j2]-s[j1])<2*s[i]

      这道题就搞定了

      注意一开始要先预处理f2,并且要从分成两段的情况开始

      注意要开long long(不然可能会炸)


    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    LL a[3100],s[3100];
    LL f1[3100],f2[3100];
    int list[3100];
    LL slop(int j1,int j2)
    {
        return (f2[j2]-f2[j1]+s[j2]*s[j2]-s[j1]*s[j1])/(s[j2]-s[j1]);
    }
    int main()
    {
        int n,m;
        scanf("%d%d",&n,&m);
        s[0]=0;
        LL sum=0;
        memset(f1,0,sizeof(f1));
        memset(f2,0,sizeof(f2));
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            sum+=a[i];
            s[i]=s[i-1]+a[i];
            f2[i]=s[i]*s[i];
        }
        for(int k=2;k<=m;k++)
        {
            int head=1,tail=1;list[head]=k-1;
            for(int i=k;i<=n;i++)
            {
                while(head<tail&&slop(list[head],list[head+1])<2*s[i]) head++;
                int j=list[head];
                f1[i]=f2[j]+(s[i]-s[j])*(s[i]-s[j]);
                while(head<tail&&slop(list[tail-1],list[tail])>slop(list[tail],i)) tail--;
                list[++tail]=i;
            }
            for(int i=1;i<=n;i++) f2[i]=f1[i];
        }
        printf("%lld
    ",m*f1[n]-sum*sum);
        return 0;
    }
  • 相关阅读:
    DripRoad(点滴之路)
    如何写优雅的代码
    .Net 一直在改变
    Protobufnet的完美解决方案
    关于msgpack序列化后的消息包是否再压缩
    失眠
    创建一个比微软性能更好空间更少的GUID
    msgpack与protobuf的简单性能测试对比
    分布式游戏服务器的登陆流程
    对象池的实现与性能测试
  • 原文地址:https://www.cnblogs.com/Never-mind/p/7899140.html
Copyright © 2020-2023  润新知