征途
题目链接:ybt金牌导航1-3-3 / luogu P4072
题目大意
有 n 个值,你要把它划分成 m 段,使得把每段的值加起来得到段的值,段值的方差最小。
输出这个最小方差乘 m^2,可证明这个是整数。
思路
.首先我们考虑化简式子:(设分段之后每一段的长度是
d
i
d_i
di,
x
‾
\overline{x}
x 是它们的平均值,即
x
‾
=
∑
i
=
1
m
d
i
m
\overline{x}=\frac{\sum_{i=1}^{m}d_i}{m}
x=m∑i=1mdi)
s
2
×
m
2
s^2\times m^2
s2×m2
=
∑
i
=
1
m
(
d
i
−
x
‾
)
2
m
×
m
2
=\dfrac{\sum\limits_{i=1}^{m}(d_i-\overline{x})^2}{m}\times m^2
=mi=1∑m(di−x)2×m2
=
∑
i
=
1
m
(
d
i
−
x
‾
)
2
×
m
=\sum\limits_{i=1}^{m}(d_i-\overline{x})^2\times m
=i=1∑m(di−x)2×m
=
∑
i
=
1
m
(
d
i
2
−
2
×
d
i
×
x
‾
+
x
‾
2
)
×
m
=\sum\limits_{i=1}^{m}(d_i^2-2\times d_i\times \overline{x}+\overline{x}^2)\times m
=i=1∑m(di2−2×di×x+x2)×m
=
m
×
∑
i
=
1
m
d
i
2
+
∑
i
=
1
m
(
−
2
×
d
i
×
∑
i
=
1
m
d
i
m
+
(
∑
i
=
1
m
d
i
m
)
2
)
×
m
=m\times\sum\limits_{i=1}^{m}{d_i^2}+\sum\limits_{i=1}^{m}(-2\times d_i\times \frac{\sum_{i=1}^{m}d_i}{m}+(\frac{\sum_{i=1}^{m}d_i}{m})^2)\times m
=m×i=1∑mdi2+i=1∑m(−2×di×m∑i=1mdi+(m∑i=1mdi)2)×m
=
m
×
∑
i
=
1
m
d
i
2
+
∑
i
=
1
m
(
−
2
×
d
i
×
∑
i
=
1
m
d
i
+
(
∑
i
=
1
m
d
i
m
)
(
∑
i
=
1
m
d
i
)
)
=m\times\sum\limits_{i=1}^{m}{d_i^2}+\sum\limits_{i=1}^{m}(-2\times d_i\times \sum_{i=1}^{m}d_i+(\frac{\sum_{i=1}^{m}d_i}{m})(\sum_{i=1}^{m}d_i))
=m×i=1∑mdi2+i=1∑m(−2×di×∑i=1mdi+(m∑i=1mdi)(∑i=1mdi))
我们再弄一个
s
u
m
=
∑
i
=
1
m
d
i
sum=\sum_{i=1}^{m}d_i
sum=∑i=1mdi。
=
m
×
∑
i
=
1
m
d
i
2
+
∑
i
=
1
m
(
−
2
×
d
i
×
s
u
m
+
(
s
u
m
m
)
×
s
u
m
)
=m\times\sum\limits_{i=1}^{m}{d_i^2}+\sum\limits_{i=1}^{m}(-2\times d_i\times sum+(\frac{sum}{m})\times sum)
=m×i=1∑mdi2+i=1∑m(−2×di×sum+(msum)×sum)
=
m
×
∑
i
=
1
m
d
i
2
+
(
−
2
)
×
∑
i
=
1
m
d
i
×
s
u
m
+
∑
i
=
1
m
(
1
m
)
×
s
u
m
2
=m\times\sum\limits_{i=1}^{m}{d_i^2}+(-2)\times \sum\limits_{i=1}^{m}d_i\times sum+\sum\limits_{i=1}^{m}(\frac{1}{m})\times sum^2
=m×i=1∑mdi2+(−2)×i=1∑mdi×sum+i=1∑m(m1)×sum2
=
m
×
∑
i
=
1
m
d
i
2
+
(
−
2
)
×
s
u
m
×
s
u
m
+
s
u
m
2
=m\times\sum\limits_{i=1}^{m}{d_i^2}+(-2)\times sum\times sum+sum^2
=m×i=1∑mdi2+(−2)×sum×sum+sum2
=
m
×
∑
i
=
1
m
d
i
2
−
2
×
s
u
m
2
+
s
u
m
2
=m\times\sum\limits_{i=1}^{m}{d_i^2}-2\times sum^2+sum^2
=m×i=1∑mdi2−2×sum2+sum2
=
m
×
∑
i
=
1
m
d
i
2
−
s
u
m
2
=m\times\sum\limits_{i=1}^{m}{d_i^2}-sum^2
=m×i=1∑mdi2−sum2
那你会发现,你就是要让 ∑ i = 1 m d i 2 \sum\limits_{i=1}^{m}d_i^2 i=1∑mdi2 最小。
那你可以设
f
i
,
j
f_{i,j}
fi,j 为前
i
i
i 个数划分成了
j
j
j 段的最小值。
那会有这个方程:
f
i
,
j
=
min
k
=
1
i
−
1
{
f
k
,
j
−
1
+
(
s
i
−
s
k
)
2
}
f_{i,j}=\min\limits_{k=1}^{i-1}\{f_{k,j-1}+(s_i-s_k)^2\}
fi,j=k=1mini−1{fk,j−1+(si−sk)2}
那可以看出它是有单调性的,那我们就直接上分治,做就好了。
代码
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
int n, m;
ll a[3001], s[3001], f[3001][3001];
void slove(int op, int l, int r, int L, int R) {//分治
int mid = (l + r) >> 1, pl = 0;
ll minn = 1e15;
for (int i = L; i <= R && i < mid; i++) {//对于处理范围中间的点用枚举答案范围求答案
ll now = f[i][op - 1] + (s[mid] - s[i]) * (s[mid] - s[i]);
if (now < minn) {
pl = i;
minn = now;
}
}
f[mid][op] = minn;
if (l < mid) slove(op, l, mid - 1, L, pl);
if (mid < r) slove(op, mid + 1, r, pl, R);
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; i++)//预处理只有一段的
f[i][1] = s[i] * s[i];
for (int i = 2; i <= m; i++)
slove(i, 1, n, 1, n);
printf("%lld", f[n][m] * m - s[n] * s[n]);//记得套回公式里
return 0;
}