洛谷 P4072 [SDOI2016]征途 斜率優化DP
洛谷 P4072 [SDOI2016]征途 斜率優化DP
題目描述
\(Pine\) 開始了從 \(S\) 地到 \(T\) 地的征途。
從\(S\)地到\(T\)地的路可以劃分成 \(n\) 段,相鄰兩段路的分界點設有休息站。
\(Pine\)計劃用\(m\)天到達\(T\)地。除第\(m\)天外,每一天晚上\(Pine\)都必須在休息站過夜。所以,一段路必須在同一天中走完。
\(Pine\)希望每一天走的路長度儘可能相近,所以他希望每一天走的路的長度的方差儘可能小。
幫助\(Pine\)求出最小方差是多少。
設方差是\(v\),可以證明,\(v\times m^2\)是一個整數。為了避免精度誤差,輸出結果時輸出\(v\times m^2\)
輸入格式
第一行兩個數 \(n\)、\(m\)。
第二行 \(n\) 個數,表示 \(n\) 段路的長度
輸出格式
一個數,最小方差乘以 \(m^2\) 後的值
輸入輸出樣例
輸入 #1
5 2
1 2 5 8 6
輸出 #1
36
說明/提示
對於 \(30\%\) 的資料,\(1 \le n \le 10\)
對於 \(60\%\) 的資料,\(1 \le n \le 100\)
對於 \(100\%\) 的資料,\(1 \le n \le 3000\)
保證從 \(S\) 到 \(T\) 的總路程不超過 \(30000\) 。
分析
\[s^2 \times m^2=\frac{(v_1-\overline{v})^2+(v_2-\overline{v})^2+...+(v_m-\overline{v})^2}m \times m^2 \]
\[s^2 \times m^2=((v_1-\overline{v})^2+(v_2-\overline{v})^2+...+(v_m-\overline{v})^2) \times m \]
\[s^2 \times m^2=m\times \sum_{i=1}^mv_i^2+m^2 \times \overline {v}^2-2\times m \times \overline {v} \times sum[n] \]
又因為$$\overline{v}=\frac{sum[n]}{m}$$
所以
\[s^2\times m^2=m\times \sum_{i=1}^mv_i^2-sum[n]\times sum[n] \]
後面的值是固定的,所以我們只需要讓前面的值最小化即可
我們設\(f[i][j]\)為前\(i\)天分成\(j\)段所得到的最小值
那麼就有
\[f[i][k]=\min(f[j][k-1]+(sum[i]-sum[j])^2) \]
展開就有
\[f[i][k]=f[j][k-1]+sum[i]^2+sum[j]^2-2\times sum[i] \times sum[j] \]
移項得
\[f[j][k-1]+sum[j]^2=2\times sum[i] \times sum[j]+f[i][k]-sum[i]^2 \]
可以用斜率優化
我們把\(f[j][k-1]+sum[j]^2\)看成\(y\)
把\(2 \times sum[i]\)看成\(k\)
把\(sum[j]\)看成\(x\)
把\(f[i][k]-sum[i]^2\)看成\(b\)
這樣,對於每一個\(i\)來說,直線的\(k\)是確定的
我們要使\(f[i][k]\)最小,也就是要使\(b\)最小
我們可以把所有的\(j\)想象成空間中的點
知道了斜率,知道了直線上的點,那麼這條直線就確定了
那麼我們考慮什麼樣的點使直線的\(b\)最大
直線\(l\)是我們要移動的直線,平面中的點是可以轉移的\(j\)值
我們會發現噹噹前點和後一個點形成的直線的斜率恰好大於直線\(l\)的斜率是,由當前點轉移決策是最優的
這就是程式碼裡面的
while(head<tail && xl(q[head],q[head+1])<2*sum[j]) head++;
f[j]=g[q[head]]+sum[j]*sum[j]+sum[q[head]]*sum[q[head]]-2*sum[j]*sum[q[head]];
我們再去考慮什麼樣的點肯定不會對結果產生貢獻
上面的圖中\(2\)號節點是無論如何也不會更新其它節點的
因為\(1\)號節點或\(3\)號節點總會比它更優
這就是程式碼裡的
while(head<tail && xl(q[tail-1],q[tail])>=xl(q[tail],i)) tail--;
整個過程就相當於維護了一個下凸包
但是,如果斜率不是單調遞增,我們就不能從前面清空佇列直接轉移,只能二分答案
比如上面這幅圖如果我們一直從前清空佇列的話那麼就會把\(2\)號決策點彈出佇列
但是如果之後遇到一個斜率比較小的直線\(m\)那麼就不能轉移到最優解
程式碼
#include<cstdio>
#include<cstring>
inline int read(){
int x=0,fh=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=1e6+5;
int a[maxn],sum[maxn],n,m,f[maxn],g[maxn],q[maxn],head,tail;
double xl(int i,int j){
return (double)(g[i]+sum[i]*sum[i]-g[j]-sum[j]*sum[j])/(double)(sum[i]-sum[j]);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
sum[i]=sum[i-1]+a[i];
g[i]=sum[i]*sum[i];
}
for(int i=1;i<m;i++){
head=tail=1;
q[1]=i;
for(int j=i+1;j<=n;j++){
while(head<tail && xl(q[head],q[head+1])<2*sum[j]) head++;
f[j]=g[q[head]]+sum[j]*sum[j]+sum[q[head]]*sum[q[head]]-2*sum[j]*sum[q[head]];
while(head<tail && xl(q[tail],q[tail-1])>xl(q[tail-1],j)) tail--;
q[++tail]=j;
}
for(int j=1;j<=n;j++) g[j]=f[j];
}
printf("%d\n",f[n]*m-sum[n]*sum[n]);
return 0;
}