題目的意思就是給出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;
}