BZOJ4518 && SDOi2016 征途

題目的意思就是給出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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章