首页 > 代码库 > 斜率优化DP
斜率优化DP
前置知识
比记号 与 拓展比记号: http://www.cnblogs.com/Sdchr/p/7347799.html
[HDU 3507] Print Article
以 [HDU 3507] 来说明清楚斜率优化.
序列 $s_1, s_2, ..., s_n$ 满足 $s_1 \le s_2 \le ... \le s_n$ .
$f_0 = 0$ .
$f_i = \min_{0 \le j < i} \left\{ f_j + {(s_i - s_j)} ^ 2 + M \right\}$ .
求 $f_n$ .
$1 \le n <= 500000$ .
$0 \le M, c_i \le 1000$ .
较优决策点的性质
较优决策点的性质
决策点 $k > j$ , $k$ 比 $j$ 优.
令 $y_i = f_i + s_i ^ 2$ .
$[f_j + {(s_i - s_j)} ^ 2 + M, f_k + {(s_i - s_k)} ^ 2 + M] = [(f_j + s_j ^ 2) - (f_k + s_k ^ 2), 2s_i(s_k - s_j)] = [y_k - y_j, 2s_i(s_k - s_j)] < 0$ .
当 $s_k = s_j$ 时, $y_k < y_j$ .
当 $s_k > s_j$ 时, $\frac{y_k - y_j}{s_k - s_j} < 2s_i$ .
令二维平面上的点 $P_i = (s_i, y_i)$ .
当 $s_k = s_j$ 时, $y_k < y_j$ , $P_k$ 在 $P_j$ 下面.
当 $s_k > s_j$ 时, $slope(P_k, P_j) < 2s_i$ .
类似的结论
$k < j$ , $k$ 比 $j$ 优 $\Leftrightarrow [y_k - y_j, 2s_i(s_k - s_j)] < 0$ .
类似的, 我们可以得到:
当 $s_k = s_j$ 时, $y_k \ge y_j$ , $P_k$ 在 $P_j$ 下面.
当 $s_k > s_j$ 时, $slope(P_k, P_j) > 2s_i$ .
最优决策点的性质
设其中一个最优决策点为 $k$ .
过 $k$ 作斜率为 $2s_i$ 的直线 $L$ .
根据较优决策点的性质, 以及特判与其余最优决策点的关系, 我们得到:
对于 $k > j$ , $P_j$ 在 $L$ 上, 或者 $L$ 上方.
对于 $k < j$ , $P_j$ 在 $L$ 上, 或者 $L$ 上方.
所以对于任意 $j$ , $P_j$ 在 $L$ 上, 或者 $L$ 上方.
最优决策点可能有多个, 但它们一定在一条直线上.
如果出现了三点贡献, 那么中间的节点可以不用处理.
所以一定有一个最优决策点在下凸壳上.
单个问题的解决
假设当前我们有所有决策点在二维平面上对应点形成的下凸壳, 那么如何求其中一个最优决策点.
记下凸壳上的第 $k$ 个点为 $g_k$ .
如果过决策点 $g_k$ 作斜率为 $2s_i$ 的直线.
我们发现:
对于最优决策点, 以及最优决策点右边的决策点, $g_kg_{k+1}$ 在 $2s_i$ 对应的方向向量上方.
对于最优决策点左边的决策点, $g_kg_{k+1}$ 在 $k$ 对应的方向向量下方.
等于号可以任意放置.
我们将 "$g_kg_{k+1}$ 在 $2s_i$ 对应的方向向量上方" , 设置成合法.
那么我们目标就是找到合法的最小的坐标, 通过二分即可轻易实现.
解法1 维护下凸壳 + 二分
由于 $s_i \ge s_{i-1}$ .
所以我们相当于每次在下凸壳的右端插入一个决策点, 然后维护下凸壳, 并用上述方法二分求解即可.
时间复杂度为 $O(n \log n)$ .
实现:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #define F(i, a, b) for (register int i = (a); i <= (b); i++) #define LL long long inline LL rd(void) { LL f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1; LL x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f; } const int N = 500005; int n, M; LL s[N]; LL f[N]; inline LL T(int Dec, int tar) { return f[Dec] + (s[tar] - s[Dec]) * (s[tar] - s[Dec]) + M; } struct point { LL x, y, id; friend inline point operator - (point A, point B) { return (point){A.x - B.x, A.y - B.y, 0}; } friend inline LL det(point A, point B) { return A.x * B.y - A.y * B.x; } }q[N]; int top; inline void Add(point w) { while (top >= 2 && det(w - q[top-1], q[top] - q[top-1]) >= 0) q[top--] = (point){0, 0, 0}; q[++top] = w; } inline int Find(point dir) { if (top == 1 || det(dir, q[top] - q[top-1]) <= 0) return q[top].id; int L = 1, R = top-1; while (L < R) { int M = (L+R)>>1; det(dir, q[M+1] - q[M]) > 0 ? R = M : L = M+1; } return q[L].id; } int main(void) { #ifndef ONLINE_JUDGE freopen("hdu3507.in", "r", stdin); #endif while (~scanf("%d %d", &n, &M)) { memset(s, 0, sizeof s); F(i, 1, n) s[i] = s[i-1] + rd(); memset(f, 0, sizeof f), memset(q, 0, sizeof q), top = 0; Add((point){s[0], f[0] + s[0] * s[0], 0}); F(i, 1, n) { int Dec = Find((point){1, 2 * s[i], 0}); f[i] = T(Dec, i); Add((point){s[i], f[i] + s[i] * s[i], i}); } printf("%lld\n", f[n]); } return 0; }
斜率优化DP