BZOJ4518: [Sdoi2016]征途

Posted Star_Feel

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ4518: [Sdoi2016]征途相关的知识,希望对你有一定的参考价值。

【传送门:BZOJ4518


简要题意:

  给出n个数,将n个数分成m段,求出这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\n",m*f1[n]-sum*sum);
    return 0;
}

以上是关于BZOJ4518: [Sdoi2016]征途的主要内容,如果未能解决你的问题,请参考以下文章

●BZOJ 4518 [Sdoi2016]征途

BZOJ4518:[SDOI2016]征途——题解

BZOJ4518: [Sdoi2016]征途

BZOJ4518: [Sdoi2016]征途(dp+斜率优化)

BZOJ 4518 [Sdoi2016]征途(分治DP)

BZOJ4518: [Sdoi2016]征途