题目
把一个长度为 n n n的序列分成 m m m份,使它的方差最小
分析
首先要剖析方差公式,设
s
u
m
[
m
]
=
∑
i
=
1
m
a
[
i
]
sum[m]=\sum_{i=1}^ma[i]
sum[m]=∑i=1ma[i]
S
2
m
2
=
m
∑
i
=
1
m
(
a
[
i
]
−
a
v
e
)
2
S^2m^2=m\sum_{i=1}^m(a[i]-ave)^2
S2m2=mi=1∑m(a[i]−ave)2
那么
S
2
m
2
=
m
∑
i
=
1
m
(
a
[
i
]
)
2
−
2
s
u
m
[
n
]
∑
i
=
1
m
a
[
i
]
+
(
s
u
m
[
n
]
)
2
S^2m^2=m\sum_{i=1}^{m}(a[i])^2-2sum[n]\sum_{i=1}^ma[i]+(sum[n])^2
S2m2=mi=1∑m(a[i])2−2sum[n]i=1∑ma[i]+(sum[n])2
实际上
∑
i
=
1
n
a
[
i
]
=
s
u
m
[
n
]
\sum_{i=1}^{n}a[i]=sum[n]
∑i=1na[i]=sum[n],所以
S
2
m
2
=
m
∑
i
=
1
m
(
a
[
i
]
)
2
−
(
s
u
m
[
n
]
)
2
S^2m^2=m\sum_{i=1}^{m}(a[i])^2-(sum[n])^2
S2m2=mi=1∑m(a[i])2−(sum[n])2
然后移项得到
S
2
m
2
+
(
s
u
m
[
n
]
)
2
=
m
∑
i
=
1
m
(
a
[
i
]
)
2
S^2m^2+(sum[n])^2=m\sum_{i=1}^{m}(a[i])^2
S2m2+(sum[n])2=mi=1∑m(a[i])2
那么终于到正题了,也就是说把
n
n
n个数分成
m
m
m份,使平方和最小
所以说就是斜率优化,设
f
[
n
]
,
g
[
n
]
f[n],g[n]
f[n],g[n]分别表示该份和上一份的状态,那么
f
[
i
]
=
g
[
q
[
h
e
a
d
]
]
+
(
s
u
m
[
i
]
−
s
u
m
[
q
[
h
e
a
d
]
]
)
2
f[i]=g[q[head]]+(sum[i]-sum[q[head]])^2
f[i]=g[q[head]]+(sum[i]−sum[q[head]])2
揭开之后发现它维护的是上凸壳,然后用单调队列维护即可
代码
#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
#define p(x) ((x)*(x))
#define t(x) (g[x]+p(sum[x]))
using namespace std;
int n,m,q[3001]; long long f[3001],g[3001],sum[3001];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline double slope(int i,int j){return (t(j)-t(i))*1.0/(sum[j]-sum[i]);}
signed main(){
n=iut(); m=iut();
for (rr int i=1;i<=n;++i) sum[i]=sum[i-1]+iut(),g[i]=p(sum[i]);
for (rr int j=1;j<m;++j){
rr int head=1,tail=1; q[1]=j;
for (rr int i=j+1;i<=n;++i){
while (head<tail&&slope(q[head],q[head+1])<2*sum[i]) ++head;
f[i]=g[q[head]]+p(sum[i]-sum[q[head]]);
while (head<tail&&slope(q[tail-1],q[tail])>slope(q[tail],i)) --tail;
q[++tail]=i;
}
memcpy(g,f,sizeof(f));
}
printf("%lld",f[n]*m-p(sum[n]));
return 0;
}