【SDOI 2016】征途

传送门


Problem

给出一个有 n n n 个数的序列 { a n } \{a_n\} {an},现要把它分成 m m m 段,设每段的权值为该段 a i a_i ai 之和,求出这 m m m 段的最小方差。

设方差是 v v v,可以证明, v × m 2 v\times m^2 v×m2 是一个整数。为了避免精度误差,输出结果时输出 v × m 2 v\times m^2 v×m2

数据范围: 1 ≤ n ≤ 3000 1 \le n \le 3000 1n3000 ∑ a i ≤ 30000 \sum a_i\le30000 ai30000


Solution

这道题和序列分割是差不多的思路。

假设 b i b_i bi 为每段的权值,用 b ‾ \overline b b 表示平均数,我们要最小化的是这个式子:

∑ i = 1 m ( b i − b ‾ ) 2 m × m 2 \frac{\sum_{i=1}^m(b_i-\overline b)^2}{m}\times m^2 mi=1m(bib)2×m2

把平方拆开,就是:

m ( ∑ i = 1 m ( b i 2 + b ‾ 2 − 2 b i b ‾ ) ) m(\sum_{i=1}^m(b_i^2+\overline b^2-2b_i\overline b)) m(i=1m(bi2+b22bib))

那继续化简,并代入 b ‾ = ∑ i = 1 m b i m \overline b=\frac{\sum_{i=1}^mb_i}{m} b=mi=1mbi,不难得到(我就直接化到最后一步了):

m ∑ i = 1 m ( b i 2 ) − ( ∑ i = 1 m b i ) 2 m\sum_{i=1}^m(b_i^2)-(\sum_{i=1}^mb_i)^2 mi=1m(bi2)(i=1mbi)2

后面那一块是定值,我们现在就要最小化 ∑ i = 1 m ( b i 2 ) \sum_{i=1}^m(b_i^2) i=1m(bi2)

F i , j F_{i,j} Fi,j 表示前 i i i 个数分成 j j j 段的最小值,设 S i S_i Si a i a_i ai 的前缀和,那么有以下转移:

F i , k = min ⁡ j = 1 i − 1 { F j , k − 1 + ( S i − S j ) 2 } F_{i,k}=\min_{j=1}^{i-1}\{F_{j,k-1}+(S_i-S_j)^2\} Fi,k=j=1mini1{Fj,k1+(SiSj)2}

这也可以滚动数组优化,设 F i , k F_{i,k} Fi,k f i f_i fi F j , k − 1 F_{j,k-1} Fj,k1 g j g_j gj,即:

f i = min ⁡ j = 1 i − 1 { g j + ( S i − S j ) 2 } f_i=\min_{j=1}^{i-1}\{g_j+(S_i-S_j)^2\} fi=j=1mini1{gj+(SiSj)2}

k &lt; j &lt; i k&lt;j&lt;i k<j<i 且用 j j j 转移比 k k k 优,则有:

g j + ( S i − S j ) 2 &lt; g k + ( S i − S k ) 2 g_j+(S_i-S_j)^2&lt;g_k+(S_i-S_k)^2 gj+(SiSj)2<gk+(SiSk)2

化简之后就是:

( g j + S j 2 ) − ( g k + S k 2 ) S j − S k &lt; 2 S i \frac{(g_j+S_j^2)-(g_k+S_k^2)}{S_j-S_k}&lt;2S_i SjSk(gj+Sj2)(gk+Sk2)<2Si

于是维护一个下凸壳然后斜率优化即可。


Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 3005
#define ll long long
using namespace std;
int n,m,Q[N];
ll f[N],g[N],S[N];
ll Squ(ll x)  {return x*x;}
ll ordi(int x)  {return g[x]+S[x]*S[x];}
double slope(int x,int y)  {return 1.0*(ordi(y)-ordi(x))/(S[y]-S[x]);}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;++i){
		scanf("%d",&x),S[i]=S[i-1]+x;
	}
	for(int i=1;i<=n;++i)  g[i]=S[i]*S[i];
	for(int j=1;j<m;++j){
		int l=0,r=0;
		for(int i=1;i<=n;++i){
			while(l<r&&slope(Q[l],Q[l+1])<=2*S[i])  l++;
			f[i]=g[Q[l]]+Squ(S[i]-S[Q[l]]);
			while(l<r&&slope(Q[r-1],Q[r])>=slope(Q[r],i))  r--;
			Q[++r]=i;
		}
		memcpy(g,f,sizeof(g));
	}
	printf("%lld",f[n]*m-S[n]*S[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值