BZOJ4518 && SDOi2016 征途

来源:互联网 发布:倍增lca算法 编辑:程序博客网 时间:2024/04/30 13:23

题目的意思就是给出n段路程:
如样例:1、2、5、8、6
要求分m天走完,使得走完的方差最小,并输出最小方差*m^2。
比如第一天走:1、2、5,总计8,第二天走8、6,总计14。
已知平均值为11,那么答案等于2^2*((8-11)^2+(14-11)^2)/2=36。
我们先准备出前缀和数组sum[n]。
比如对于样例:
sum[1]=1
sum[2]=3
sum[3]=8
sum[4]=16
sum[5]=22
我们将答案进行变形:
ans
=m^2*((d1-平均)^2+(d2-平均)^2+(d3-平均)^2+……+(dm-平均)^2)/m(我们把平方打开)
=m*(d1^2+d2^2+d3^2+……+dm^2+m×平均^2-2×(d1+d2+…+dm)×平均)
=m*(d1^2+d2^2+d3^2+……+dm^2)-sum[n]×sum[n]
好了由于sum[n]是常量,我们只要使d1^2+d2^2+d3^2+……+dm^2最小即可。
动态规划f[i][j]表示走了i段,分成j天,d1^2+d2^2+d3^2+……+dj^2的最小值。
那么显而易见对于递推公式:
f[i][j]=min(f[k][j-1]+(sum[i]-sum[k])^2)其中j-1<= k < i
好了这明显是一个n^3的时间复杂度这是不能接受的。
我们考虑斜率优化。
假设 q < w 且由f[w][j-1]更新到f[i][j]比f[q][j-1]更新到f[i][j]更优,那么有以下式子
f[w][j-1]+(sum[i]-sum[w])^2 < f[q][j-1]+(sum[i]-sum[q])^2
将该式子化简:
(f[w][j]+sum[w]^2-f[q][j]+sum[q]^2)/(2*(sum[w]-sum[q])) < sum[i]
我们将这个东东看成一个斜率yw-yq/xw-xq < sum[i]
既g(w,q)=(f[w][j]+sum[w]^2-f[q][j]+sum[q]^2)/(2*(sum[w]-sum[q]))。
我们要剔除一些点:
设k < j < i,如果g(i,j) < g(j,k),那么j点便永远不可能成为最优解,可以直接将它踢出。
我们假设g(i,j) < sum[i],那么就是说i点要比j点优,排除j点。
如果g(i,j) >=sum[i],那么j点此时是比i点要更优,但是同时g(j,k)>g(i,j)>sum[i]。这说明还有k点会比j点更优,同样排除j点。
这样我们保证当前的序列中都满足如下关系:g(i,i-1) < g(i+1,i),图像是个下凸包。
这里写图片描述
我们看图中ABCD,满足g(i,i-1) < g(i+1,i),图像是个下凸包。
好了我们如何维护呢?
当我们推完f[i][j]时,并准备推f[i+1][j],那么f[i][j-1]应该加入决策点集中,这时假如f[i][j-1]是E点。
如果g(C,D)>g(D,E),我们发现D不可能成为决策点,刚才上面证的。
我们将D踢掉,并判断E能否接着踢掉C,一直到不能踢掉为止。

好了,维护搞定了,那我们如何动态规划呢?
我们正着来:从ABC…往后看。
如果g(B,A)< sum[i],那么B更优,A踢掉,由于下凸包斜率单增,那么不断向后寻找。
知道发现g(s,s-1)>=sum[i],那么说明s-1暂时是最优的就由s-1来动态规划。
具体的维护我们用队列来维护定义head与tail。
当队尾加入新的点时更新tail。
当队首要踢掉点时如g(B,A) < sum[i]时的点A,就++head。
好了见代码:

#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> #include<queue> #include<vector> #include<climits> typedef long long ll; using namespace std; inline ll read() {   char ls=getchar();for (;ls<'0'||ls>'9';ls=getchar());   ll x=0;for (;ls>='0'&&ls<='9';ls=getchar()) x=x*10+ls-'0';   return x; } ll n,m;ll a[33333];ll b[33333];ll sum[33333];ll best;ll q[33333];ll head;ll tail;inline double solo(ll x,ll y){    return ((a[x]-a[y]+sum[x]*sum[x]-sum[y]*sum[y])/(2*(sum[x]-sum[y])));}int main(){    n=read(),m=read();    for(int i=1;i<=n;++i)    sum[i]=read(),sum[i]+=sum[i-1];    for(int i=1;i<=n;++i)    a[i]=sum[i]*sum[i];    for(int j=2;j<=m;++j)    {        memset(q,0,sizeof(q));        head=1;tail=1;        q[head]=j-1;//队列q        for(int i=j;i<=n;++i)        {            //cout<<i<<":"<<endl;            //cout<<head<<" "<<tail<<endl;            //for(int i=1;i<=n;++i)            //{            //  cout<<q[i]<<" ";            //}             //cout<<endl;            while(head+1<=tail)            if(solo(q[head+1],q[head])<sum[i])//判断是否踢掉队首            ++head;            else            break;            b[i]=a[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);            while(head+1<=tail)            if(solo(i,q[tail])<solo(q[tail],q[tail-1]))//判断是否踢掉队尾            --tail;            else            break;            q[++tail]=i;        }        for(int i=1;i<=n;++i)        a[i]=b[i];//我用了滚动数组    }    ll s=m*a[n]-sum[n]*sum[n];    printf("%lld",s);    return 0;}
0 0