首页 > 代码库 > 斜率优化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