Address
BZOJ 3675
O(nk)
O
(
n
k
)
斜率优化题解
Solution
假设我们把
k
k
加强到 而不是
[1,min(n−1,200)]
[
1
,
min
(
n
−
1
,
200
)
]
。
结论:最终的得分与分割顺序无关,并且最终得分为:
(∑ni=1ai)2−∑k+1i=1b2i2
(
∑
i
=
1
n
a
i
)
2
−
∑
i
=
1
k
+
1
b
i
2
2
其中 bi b i 为第 i i 段的总分。
(具体分析见 斜率优化题解)
于是我们要最小化每一段的平方之和。
dp 模型: f[i][j] f [ i ] [ j ] 表示前 i i 个数分成 段的最小平方和。
转移显然:
f[i][j]=mink=0i−1{f[k][j−1]+(sum[i]−sum[k])2}
f
[
i
]
[
j
]
=
min
k
=
0
i
−
1
{
f
[
k
]
[
j
−
1
]
+
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
2
}
复杂度 O(n2k) O ( n 2 k ) 。经过斜率优化之后复杂度 O(nk) O ( n k ) ,在 k≤n−1 k ≤ n − 1 时无法通过。
我们考虑能否去掉第二维。
感性地理解一下,可以得到对于任意的 i,j i , j 都有 f[i][j]≥f[i][j+1] f [ i ] [ j ] ≥ f [ i ] [ j + 1 ]
且 f[i][j]−f[i][j+1]≥f[i][j+1]−f[i][j+2] f [ i ] [ j ] − f [ i ] [ j + 1 ] ≥ f [ i ] [ j + 1 ] − f [ i ] [ j + 2 ] 。
不妨给每一段一个代价 C C ,表示分成 段的额外代价是 j×C j × C ,并去掉 f f 的第二维。
根据上面的结论可以得到推论:设 取得最小值时分成 最多 q(C) q ( C ) 段,那么 q q 随 的增大而单调不增。 C=0 C = 0 时最优方案是分成 n n 段每段一个数, 时最优方案是只分一段。
于是我们考虑二分 C C ,也就是找到满足 的最大的 C C 。
的转移:
f[i]=maxj=0i−1{f[j]+(sum[i]−sum[j])2}+C
f
[
i
]
=
max
j
=
0
i
−
1
{
f
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
)
2
}
+
C
是一个简单的斜率优化 dp 。
但我们二分判定 C C 时要求 ,故还要记下 g[i] g [ i ] 表示前 i i 个数在代价最小的前提下分成的最大段数。取最优决策时,如果最优决策三点共线,则要取出 最大的点进行转移。
这种二分称为 带权二分或 凸优化。
复杂度 O(nlogC) O ( n log C ) 。
Code
判断斜率是否递增时不能写成叉积的形式,否则会爆 long long
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
T Max(T a, T b) {return a > b ? a : b;}
typedef long long ll;
const int N = 1e5 + 5;
int n, k, g[N], H, T, Q[N];
ll f[N], X[N], Y[N], sum[N];
ll calc(int j, int i)
{
return Y[j] - X[j] * sum[i] * 2;
}
bool slope(int k, int j, int i)
{
return X[j] == X[i] ? 1.0 * (X[j] - X[k]) / (Y[j] - Y[k])
<= 1.0 * (X[i] - X[k]) / (Y[i] - Y[k]) :
1.0 * (X[j] - X[k]) / (Y[j] - Y[k])
< 1.0 * (X[i] - X[k]) / (Y[i] - Y[k]);
}
void slopedp(ll mid)
{
int i;
f[0] = 0; g[0] = 0;
X[0] = Y[0] = 0;
Q[H = T = 1] = 0;
For (i, 1, n)
{
while (H < T && calc(Q[H], i) >= calc(Q[H + 1], i))
{
if (calc(Q[H], i) == calc(Q[H + 1], i))
g[Q[H + 1]] = Max(g[Q[H + 1]], g[Q[H]]);
H++;
}
f[i] = calc(Q[H], i) + sum[i] * sum[i] + mid;
g[i] = g[Q[H]] + 1;
X[i] = sum[i]; Y[i] = f[i] + sum[i] * sum[i];
while (H < T && slope(Q[T - 1], Q[T], i)) T--;
Q[++T] = i;
}
}
int main()
{
int i;
n = read(); k = read();
For (i, 1, n) sum[i] = sum[i - 1] + read();
ll l = 0, r = 1e18;
while (l <= r)
{
ll mid = l + r >> 1;
if (slopedp(mid), g[n] >= k + 1) l = mid + 1;
else r = mid - 1;
}
slopedp(r);
cout << (sum[n] * sum[n] - f[n] + r * (k + 1) >> 1) << endl;
return 0;
}