[dp 进阶] 动态 dp

[dp 进阶] 动态 dp

动态 dp(DDP, dynamic dynamic programming)一般用来解决带有点权/边权修改操作的树上问题

以下题为例讲解 ddp 的过程:

P4719 【模板】动态 DP)给定一棵带点权的树,多次修改某个点的点权,每次修改后求出树的最大权独立集。

如果不带修,这是经典的树形 dp。设 \(f(u, 1/0)\) 表示选择/不选择 \(u\) 时,\(T(u)\) 中的最大权独立集,有转移

\[\begin{aligned} f(u, 0) &= \sum_{v \in son(u)} \max(f(v, 0), f(v, 1)) \\ f(u, 1) &= a_u + \sum_{v \in son(u)} f(v, 0) \end{aligned} \]

如果每次修改都 dp 一遍,效率太低,我们考虑信息复用。一个观察是,修改一个点 \(u\) 的点权,只会改变 \(u\) 到根节点路径上的点的 dp 值。扩展这个想法,把树重链剖分,那么每次修改后,只有 \(O(\log n)\) 条链上的 dp 值会受影响。

为了更好地利用重链剖分,设状态 \(g(u, 1/0)\) 表示 \(u\) 节点删去重子以后的 dp 值。设 \(v = hson_u\),则下式成立:

\[\begin{aligned} g(u, 0) &= f(u, 0) - \max(f(v, 0), f(v, 1)) \\ g(u, 1) &= f(u, 1) - f(v, 0) \end{aligned} \]

设计这个状态的意义在于:修改点 \(u\) 的点权,不会改变 \(u\) 重链中除了 \(u\) 的点的 \(g\)。(当然,\(f\) 的值可能会被修改)设 \(p\)\(u\) 所在重链的链顶,如果能快速求出 \(f(p, 0/1)\),就可以用 \(p\)\(f\) 值更新 \(fa_p\)\(g\) 值。也就是说我们一直在重复这个过程:

  1. 修改某个点 \(u\)\(g\) 值。
  2. 求出 \(u\) 所在重链的链顶 \(p\)\(f\) 值,用来更新 \(fa_p\)\(g\) 值。这一步可以 \(O(1)\) 完成。
  3. 如果 \(u = 1\),结束修改过程,否则令 \(u \gets fa_p\),并回到第 1 步。

那么如何快速求出某个点的 \(f\) 值呢?套路性地考虑把转移写成矩阵的形式,然后用线段树维护区间矩阵积。这里的矩阵乘法定义为 \((\max, +)\) 矩阵乘法。

\[\begin{bmatrix} g(u, 0) & g(u, 0) \\ g(u, 1) & -\infty \end{bmatrix} \begin{bmatrix} f(hson_u, 0) \\ f(hson_u, 1) \end{bmatrix} = \begin{bmatrix} f(u, 0) \\ f(u, 1) \end{bmatrix} \]

其中 \(\begin{bmatrix} g(u, 0) & g(u, 0) \\ g(u, 1) & -\infty \end{bmatrix}\) 可视作转移矩阵,记为 \(A_u\)。为了便于实现,对于叶子节点 \(u\),设 $f(hson_u, 0) =0 \(,\)f(hson_u, 1) = -\infty$。(可以发现这样设计是符合转移方程的)那么,要求出 \(f(u, 0/1)\),只要从 \(u\) 所在重链的链底开始,向上依次乘经过节点的转移矩阵。设 \(p\)\(u\) 所在重链的链底,则

\[\begin{bmatrix} f(u, 0) \\ f(u, 1) \end{bmatrix} = \left( \prod_{v \in \delta(p, u)} A_{v} \right) \begin{bmatrix} 0 \\ -\infty \end{bmatrix} \]

其中 \(\delta(p, u)\) 表示 \(p\)\(u\) 的简单路径,枚举点 \(v\) 的顺序是自底向上。

那么在重链剖分之后,在线段树上查询 \([\operatorname{dfn}(u), \operatorname{dfn}(p)]\) 的矩阵乘积,就可以求出 \(f(u, 1/0)\)。令 \(u = 1\) 即可求出全局的最大独立集。对于修改操作,依照上文中的过程,修改某个点 \(u\)\(g\) 值时就在线段树上修改该点的转移矩阵,查询 \(f\) 值也可以在线段树上完成。因此单次修改的时间复杂度为 \(O(\log^2 n)\)

AC 记录

Code
#include<bits/stdc++.h>

using namespace std;

constexpr int INF = 0x3f3f3f3f;

struct Matrix {
    static const int n = 2;
    int a[n][n];

    Matrix() {
        memset(a, 0xc0, sizeof(a));
    }

    int* operator [] (int x) {
        return a[x];
    }

    const int* operator [] (int x) const {
        return a[x];
    }

    friend Matrix operator * (const Matrix &A, const Matrix &B) {
        Matrix C;
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                for(int k = 0; k < n; k++) {
                    C[i][j] = max(C[i][j], A[i][k] + B[k][j]);
                }
            }
        }
        return C;
    }
};

struct SGT {
    #define lson (id << 1)
    #define rson (id << 1 | 1)

    int n;
    vector<Matrix> info;

    void update(int id) {
        info[id] = info[lson] * info[rson];
    }

    void init(int _n, const vector<Matrix> &a) {
        n = _n, info.resize(n << 2);
        function<void(int, int, int)> build = [&](int id, int l, int r) {
            if(l == r) {
                info[id] = a[l];
                return;
            }
            int mid = (l + r) >> 1;
            build(lson, l, mid), build(rson, mid + 1, r);
            update(id);
        };
        build(1, 1, n);
    }

    void change(int id, int l, int r, int pos, const Matrix &x) {
        if(l == r) {
            info[id] = x;
            return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) {
            change(lson, l, mid, pos, x);
        } else {
            change(rson, mid + 1, r, pos, x);
        }
        update(id);
    }

    void change(int pos, const Matrix &x) {
        change(1, 1, n, pos, x);
    }

    Matrix query(int id, int l, int r, int L, int R) {
        if(L == l && R == r) {
            return info[id];
        }
        int mid = (l + r) >> 1;
        if(R <= mid) {
            return query(lson, l, mid, L, R);
        } else if(L > mid) {
            return query(rson, mid + 1, r, L, R);
        } else {
            return query(lson, l, mid, L, mid) * query(rson, mid + 1, r, mid + 1, R);
        }
    }

    Matrix query(int L, int R) {
        return query(1, 1, n, L, R);
    }

    #undef lson
    #undef rson
}tr;

int n, m;
vector<vector<int>> G;
vector<int> a;

vector<array<int, 2>> f, g;
vector<int> hson, fa, top, bot, dfn, sz;
int dn;

void dfs1(int u = 1) {
    sz[u] = 1, f[u][1] = a[u];
    for(int v: G[u]) {
        if(v == fa[u]) continue;
        fa[v] = u;
        dfs1(v);

        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0];

        sz[u] += sz[v];
        if(sz[v] > sz[hson[u]]) {
            hson[u] = v;
        }
    }
}

void dfs2(int u = 1, int _top = 1) {
    dfn[u] = ++dn;
    top[u] = _top, bot[u] = dn;
    g[u][0] = f[u][0], g[u][1] = f[u][1];

    if(hson[u]) {
        int v = hson[u];
        dfs2(v, _top);
        g[u][0] -= max(f[v][0], f[v][1]);
        g[u][1] -= f[v][0];
        bot[u] = bot[v];
    }

    for(int v: G[u]) {
        if(v == fa[u] || v == hson[u]) continue;
        dfs2(v, v);
    }
}

Matrix make(const array<int, 2> &arr) {
    Matrix b;
    b[0][0] = arr[0], b[0][1] = arr[0];
    b[1][0] = arr[1], b[1][1] = -INF;
    return b;
}

void update(int u, int x) {
    g[u][1] += x - a[u];
    a[u] = x;
    while(u) {
        int p = top[u];
        auto lst = tr.query(dfn[p], bot[p]);
        tr.change(dfn[u], make(g[u]));
        auto now = tr.query(dfn[p], bot[p]);

        u = fa[p];
        int x0 = lst[0][0], x1 = lst[1][0];
        int y0 = now[0][0], y1 = now[1][0];
        g[u][0] += max(y0, y1) - max(x0, x1);
        g[u][1] += y0 - x0;
    }
}

int solve() {
    auto res = tr.query(1, bot[1]);
    return max(res[0][0], res[1][0]);
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);

    cin >> n >> m;
    a.resize(n + 1);
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    G.resize(n + 1);
    for(int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        G[u].push_back(v), G[v].push_back(u);
    }

    f.resize(n + 1), g = f;
    hson.resize(n + 1), top = fa = bot = sz = dfn = hson;
    dfs1(), dfs2();

    vector<Matrix> init(n + 1);
    for(int i = 1; i <= n; i++) {
        init[dfn[i]] = make(g[i]);
    }

    tr.init(n, init);
    for(int i = 1, u, x; i <= m; i++) {
        cin >> u >> x;
        update(u, x);
        cout << solve() << '\n';
    }

    return 0;
}

例题

I. P5024 [NOIP 2018 提高组] 保卫王国

和模板极其相似。

\(f(u, 1/0)\) 表示选择/不选择 \(u\) 时,\(T(u)\) 中的最小点覆盖,\(g(u, 1/0)\) 表示删去重子之后的 dp 值。

首先列出最小点覆盖的状态转移方程,推出转移矩阵。某个点 \(u\) 必须选的限制,等价于令 \(f(u, 0) \gets +\infty\),同理不选的限制相当于令 \(f(u, 1) \gets +\infty\)。由于线段树维护的是 \(g\),所以我们应该分别令 \(g(u, 0) \gets +\infty\)\(g(u, 1) \gets +\infty\),这是等价的。剩下的部分和模板相同。

参考代码

posted @ 2025-06-03 20:06  DengStar  阅读(31)  评论(1)    收藏  举报