[BZOJ4518][SDOI2016]征途
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;
}