[SDOI2016]征途(斜率優化)
阿新 • • 發佈:2021-08-13
簡單的斜率優化題目,但初學時寫程式碼好像遇到了麻煩……
,得 \(m\sum_{i=1}^{m}{{x_i}^2}-\sum_{i=1}^{m}{{x_i}^2}\) .
到 \(i\) 段路,把它加進整體的最小平方和。時間複雜度為 \(O(n^2m)\)
在一個風雨如晦的晚上, q0000000 同學被一道紫題切爆了。
一、 關於思路
q0000000 是奔著斜率優化來刷題的,如果他在考場上遇到這道題,他就得先推完式子才知道了。
- 路的長度和天數,這似乎是兩個可以用的狀態;
- 題目裡出現了方差這種奇怪的東西,應該從這裡開始推式子;
- 題目給出了 \(v \times m^2\) 的輸出形式,推式子的時候顯然要把它乘上——為什麼一開始沒乘呢?
q0000000 想要先推一個暴力式子,然後優化它。
二、 關於式子
從方差的定義出發 \(s^2=\frac{1}{m}\sum_{i=1}^{n}{(x_i - \overline{x})^2}\) .
乘上 \(m^2\)
不難發現,後半部分是一個常量, \(m\) 也可以最後一起乘,真正要關注的只有 \(\sum_{i=1}^{m}{{x_i}^2}\) 。dp陣列記錄的也就是它——最小平方和,這可以用字首和處理一下。
定義 \(f_{k,i}\) 為前 \(i\) 段路走了 \(k\) 天,從第一天到現在,每天走的路徑長度之和的最小平方和。
dp式子得到了,\(f_{k,i}=min\{f_{k-1,j}+(s_i-s_j)^2\}\)。式子中的 \(s_i-s_j\) 表示第 \(k\) 天走的距離,這一天走了第 \(j+1\)
轉化為斜率優化形式反而較為簡單, \(f_j+{s_j}^2=2\times s_i\times s_j+(f_i-{s_i}^2)\) 。 \(y,x,k,b\) 不必贅述。時間複雜度 \(O(nm)\) 。
三、 關於爆零
- 初始化 f 陣列,第一天走任意遠,否則後面會少情況。求字首和順便
f[1][i]=s[i]*s[i]
,列舉天數從 2 開始。 - 回看暴力思路,列舉天數,列舉這一天走到哪,列舉昨天走到哪,“每一天都是一個全新開始”,注意清空佇列。
for(int k=2;k<=m;++k){ int l=1,r=1; //列舉i }
- 其他斜率優化細節。
四、 關於程式碼
#include<stdio.h>
typedef long double db;
typedef long long ll;
const int N=3000+10;
int n,m;
ll s[N],f[N][N],q[N];
inline ll rd(){
ll x=0;int f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f^=1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f?x:-x;
}
inline db k(int p,ll a,ll b){
db Y=(f[p][a]+s[a]*s[a])-(f[p][b]+s[b]*s[b]);
db X=s[a]-s[b];
return Y/X;
}
int main(){
n=rd(),m=rd();
for(int i=1;i<=n;++i){
s[i]=rd();
s[i]+=s[i-1],f[1][i]=s[i]*s[i];
}
for(int j=2;j<=m;++j){
int l=1,r=1;
for(int i=1;i<=n;++i){
while(l<r&&k(j-1,q[l],q[l+1])<=2*s[i]) ++l;
f[j][i]=f[j-1][q[l]]+(s[i]-s[q[l]])*(s[i]-s[q[l]]);
while(l<r&&k(j-1,q[r-1],q[r])>=k(j-1,i,q[r])) --r;
q[++r]=i;
}
}
printf("%lld\n",m*f[m][n]-s[n]*s[n]);
return 0;
}