1. 程式人生 > 其它 >[SDOI2016]征途(斜率優化)

[SDOI2016]征途(斜率優化)

簡單的斜率優化題目,但初學時寫程式碼好像遇到了麻煩……

在一個風雨如晦的晚上, q0000000 同學被一道紫題切爆了。

這裡是題目傳送門和千辛萬苦後的AC記錄

一、 關於思路

q0000000 是奔著斜率優化來刷題的,如果他在考場上遇到這道題,他就得先推完式子才知道了。

  1. 路的長度和天數,這似乎是兩個可以用的狀態;
  2. 題目裡出現了方差這種奇怪的東西,應該從這裡開始推式子
  3. 題目給出了 \(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}-\sum_{i=1}^{m}{{x_i}^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\)

\(i\) 段路,把它加進整體的最小平方和。時間複雜度為 \(O(n^2m)\)

轉化為斜率優化形式反而較為簡單, \(f_j+{s_j}^2=2\times s_i\times s_j+(f_i-{s_i}^2)\)\(y,x,k,b\) 不必贅述。時間複雜度 \(O(nm)\)

三、 關於爆零

  1. 初始化 f 陣列,第一天走任意遠,否則後面會少情況。求字首和順便 f[1][i]=s[i]*s[i] ,列舉天數從 2 開始。
  2. 回看暴力思路,列舉天數,列舉這一天走到哪,列舉昨天走到哪,“每一天都是一個全新開始”,注意清空佇列。
    for(int k=2;k<=m;++k){
    	int l=1,r=1;
    	//列舉i                  
    }
    
  3. 其他斜率優化細節。

四、 關於程式碼

#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;
}

THE END