Loj#2035-[SDOI2016]征途【斜率優化】

正題

題目鏈接:https://loj.ac/problem/2035


題目大意

nn個數字分成mm段,要求方差最小。


解題思路

首先方差的公式i=1n(xix)2\sum_{i=1}^n(x_i-|x|)^2
其中x|x|是不變的,定義w=xw=|x|
fi,jf_{i,j}表示已經分到第ii段,到第jj個時的最小方差和。

做前綴和si=j=1iais_i=\sum_{j=1}^ia_i
之後有fk,i=min{fk1,j+(sisj)2+w22(sisj)w}f_{k,i}=min\{f_{k-1,j}+(s_i-s_j)^2+w^2-2(s_i-s_j)w\}
去掉minmin拆括號
fk,i=fk1,j+si22sisj+sj2+w22siw+sjwf_{k,i}=f_{k-1,j}+s_i^2-2s_is_j+s_j^2+w^2-2s_iw+s_jw
fk,isi2+siw+2sisj2sjw=fk1,j+sj2f_{k,i}-s_i^2+s_iw+2s_is_j-2s_jw=f_{k-1,j}+s_j^2
fk,if_{k,i}最小就是fk,isi2+siwf_{k,i}-s_i^2+s_iw最小,後爲了方便
定義F=fk,isi2+siwF=f_{k,i}-s_i^2+s_iw
F+2(siw)sj=fk1,j+sj2F+2(s_i-w)s_j=f_{k-1,j}+s_j^2
然後有若干個決策點(sj,fk1,j+sj2)(s_j,f_{k-1,j}+s_j^2)
每次有一條直線y=2(siw)x+Fy=2(s_i-w)x+F經過某個決策點要求FF最小
顯然因爲siws_i-w的單調性和sjs_j的單調性我們可以使用單調隊列維護一個下凸殼。

時間複雜度O(nm)O(nm)


codecode

#include<cstdio>
#include<cstring>
#include<algorithm>
#define pow2(x) ((x)*(x))
using namespace std;
const int N=3100;
struct node{
	double x,y;
	int num;
}q[N];
int n,m;
double s[N],f[N][N];
double slope(node x,node y)
{return (y.y-x.y)/(y.x-x.x);}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lf",&s[i]),s[i]=s[i]*m+s[i-1];
	double w=s[n]/m;
	for(int i=1;i<=n;i++)
		f[1][i]=pow2(s[i]-w);
	for(int k=2;k<=m;k++){
		int head=1,tail=1;
		q[1]=(node){s[k-1],f[k-1][k-1]+pow2(s[k-1]),k-1};
		for(int i=k;i<=n;i++){
			int z=2*(s[i]-w);
			while(head<tail&&slope(q[head],q[head+1])<z)head++;
			int p=q[head].num;
			f[k][i]=f[k-1][p]+pow2(s[i]-s[p]-w);
			node po=(node){s[i],f[k-1][i]+pow2(s[i]),i};
			while(head<tail&&slope(po,q[tail])<slope(q[tail-1],q[tail]))
				tail--;
			q[++tail]=po;
		}
	}
	printf("%.0lf",f[m][n]/m);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章