description
你要把一个长度为(n)的数列划分为(m)段,最小化每一段之和的方差。
(1 le m le n le 3000)
sol
设第(i)段的和为(S_i),数列中的元素总和为(X),那么
[mbox{ans}=msum_{i=1}^m(S_i-frac Xm)^2=msum_{i=1}^mS_i^2-X^2
]
所以只需要考虑最小化(sum_{i=1}^mS_i^2)就好啦。
这个显然可以做(O(nm))的斜率优化。
然后这里的答案也显然是凸的,所以可以凸优化一下。
于是我们就愉快地用(O(nlog 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
",1ll*S*m-1ll*s[n]*s[n]);
return 0;
}