题意
给出N个单词,每个单词有个非负权值ai,现在要将它们分成连续的若干段,每段的代价为此段单词的权值和的平方,还要加一个常数M,即 ( ∑ i = 1 k a i ) 2 + M (\sum_{i=1}^{k}ai)^2+M (∑i=1kai)2+M。现在想求出一种最优方案,使得总费用之和最小。
思路
经典题型。
数组s表示权值的前缀和,数组f表示前i个单词分组的最小代价。可以列出方程
f
[
i
]
=
m
i
n
{
(
s
[
i
]
−
s
[
j
]
)
2
+
M
+
f
[
j
]
}
f[i]=min\{(s[i]-s[j])^2+M+f[j]\}
f[i]=min{(s[i]−s[j])2+M+f[j]}
化简,移项后得到
f
[
i
]
−
s
[
i
]
2
−
M
=
f
[
j
]
+
s
[
j
]
2
−
2
∗
s
[
i
]
∗
s
[
j
]
f[i]-s[i]^2-M=f[j]+s[j]^2-2*s[i]*s[j]
f[i]−s[i]2−M=f[j]+s[j]2−2∗s[i]∗s[j]
代换:
y
=
f
[
j
]
+
s
[
j
]
2
k
=
2
∗
s
[
i
]
x
=
s
[
j
]
k
=
2
∗
s
[
i
]
\begin{aligned} &y = f[j]+s[j]^2\\ &k=2*s[i]\\ &x=s[j]\\ &k = 2*s[i] \end{aligned}
y=f[j]+s[j]2k=2∗s[i]x=s[j]k=2∗s[i]
得到
y
=
k
x
+
b
y=kx+b
y=kx+b由于是取最小值,所以是维护下凸壳,使用单调队列维护。
代码
//
// Created by yjq on 2019/9/5.
//
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const int maxn = 5e5 + 10;
ll n, M, f[maxn], q[maxn], a[maxn], s[maxn];
ll X(int i, int j) {
return s[j] - s[i];
}
ll Y(int i, int j) {
return f[j] + s[j] * s[j] - (f[i] + s[i] * s[i]);
}
ll DP(int i, int j) {
return (s[i] - s[j]) * (s[i] - s[j]) + M + f[j];
}
int main() {
__;
while (cin >> n >> M) {
for (int i = 0; i <= n; ++i) {
a[i] = s[i] = q[i] = 0;
f[i] = 0;
}
for (int i = 1; i <= n; ++i) {
cin >> a[i];
s[i] = a[i] + s[i - 1];
}
int l = 0, r = 0;
q[++r] = 0;
for (int i = 1; i <= n; ++i) {
while (l + 1 < r &&
Y(q[l + 1], q[l + 2]) <= X(q[l + 1], q[l + 2]) * 2ll * s[i])
++l;
f[i] = DP(i, q[l + 1]);
while (l + 1 < r &&
Y(q[r - 1], q[r]) * X(q[r], i) >=
X(q[r - 1], q[r]) * Y(q[r], i))
--r;
q[++r] = i;
}
cout << f[n] << endl;
}
return 0;
}