[BZOJ4518][SDOI2016]征途

bzoj
luogu

description

你要把一个长度为\(n\)的数列划分为\(m\)段,最小化每一段之和的方差。
\(1 \le m \le n \le 3000\)

sol

设第\(i\)段的和为\(S_i\),数列中的元素总和为\(X\),那么

\[\mbox{ans}=m\sum_{i=1}^m(S_i-\frac Xm)^2=m\sum_{i=1}^mS_i^2-X^2 \]

所以只需要考虑最小化\(\sum_{i=1}^mS_i^2\)就好啦。
这个显然可以做\(O(nm)\)的斜率优化。
然后这里的答案也显然是凸的,所以可以凸优化一下。
于是我们就愉快地用\(O(n\log X)\)的复杂度过掉了一道\(n=3000\)的题啦。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 30005;
struct info{
	int f,k;
	info operator + (const info &b) const
		{return (info){f+b.f,k+b.k};}
}dp[N];
int n,m,s[N],q[N],hd,tl;
int y(int i){return dp[i].f+s[i]*s[i];}
void solve(int c){
	hd=tl=0;
	for (int i=1;i<=n;++i){
		while (hd<tl&&y(q[hd+1])-y(q[hd])<2*s[i]*(s[q[hd+1]]-s[q[hd]])) ++hd;
		dp[i]=dp[q[hd]]+(info){(s[i]-s[q[hd]])*(s[i]-s[q[hd]])+c,1};
		while (hd<tl&&1ll*(y(i)-y(q[tl]))*(s[q[tl]]-s[q[tl-1]])<1ll*(y(q[tl])-y(q[tl-1]))*(s[i]-s[q[tl]])) --tl;
		q[++tl]=i;
	}
}
int main(){
	n=gi();m=gi();
	for (int i=1;i<=n;++i) s[i]=s[i-1]+gi();
	int l=0,r=s[n]*s[n],mid;
	while (l<r){
		mid=l+r>>1;solve(mid);
		if (dp[n].k<=m) r=mid;else l=mid+1;
	}
	solve(r);int S=dp[n].f-m*r;
	printf("%lld\n",1ll*S*m-1ll*s[n]*s[n]);
	return 0;
}
posted @ 2018-08-06 10:21  租酥雨  阅读(296)  评论(0编辑  收藏  举报