[学习笔记]动态动态规划/动态DP/DDP

概述

\(DDP\)是指一类需要支持修改的\(DP\)问题,常见的主要是带修树形\(DP\),可以用树链剖分结合矩阵乘法优化修改的复杂度

详解

从例题来分析:洛谷P4719

题目大意:给出\(n\)个点的树,每个点有点权,共\(m\)次操作,每次修改一个点的点权,求每次修改后树的最大权独立集的权值大小

\(n, m \le 1e5\)


如果不带修改,容易想到设\(f_{u, 0/1}\)来表示以\(u\)为根的子树,选/不选\(u\)的答案,推出转移:
\[ \begin{align} f_{u, 0} & = \sum_{v \in son_u} \max(f_{v, 0}, f_{v, 1}) \\ f_{u, 1} & = w_u + \sum_{v \in son_u} f_{v, 0} \end{align} \]
答案就是\(\max (f_{1, 0}, f_{1, 1})\)

但是这样单次修改需要修改到根的路径上的所有点,是\(O(n)\)

既然修改的是一条路径,不妨试试树链剖分

但是按上面的方式转移显然不能将一条链上的转移合并,所以考虑重新设计一下状态,\(f\)的含义不变,增加一个\(g_{u, 0/1}\)表示只考虑\(u\)的轻子树的答案,那么就有:
\[ \begin{align} g_{u, 0} & = \sum_{v \in lson_u} \max(f_{v, 0}, f_{v, 1}) \\ g_{u, 1} & = w_u + \sum_{v \in lson_u} f_{v, 0} \\ f_{u, 0} & = g_{u, 0} + \max(f_{hv[u], 0}, f_{hv[u], 1}) \\ f_{u, 1} & = g_{u, 1} + f_{hv[u], 0} \end{align} \]

观察后两式中\(f\)\(g\)的关系我们发现貌似可以写成形如矩阵乘法的形式,如果我们重新定义矩阵乘法为\(C_{i, j} = \max_{k = 1}^{n} (A_{i, k} + B_{k, j})\),就会有:
\[ \left[ \begin{matrix} f_{hv[u], 0} & f_{hv[u], 1} \end{matrix} \right] * \left[ \begin{matrix} g_{u, 0} & g_{u, 1} \\ g_{u, 0} & -\infty \end{matrix} \right] = \left[ \begin{matrix} f_{u, 0} & f_{u, 1} \end{matrix} \right] \]
容易证明新定义的矩阵乘法具有结合律,那么就可以树链剖分后用线段树维护重链上第二个矩阵的积

于是我们可以发现:

  1. 如果一个点是链顶,它的\(f\)会对它父亲的\(g\)产生贡献
  2. 否则,它的\(g\)会对它所在链的链顶产生贡献,且已经统计进线段树中

所以我们只需要线段树维护\(g\)的积,同时维护下每条链链顶的\(f\)就行了

具体做法就是先在线段树中更新当前节点的\(g\),然后更新当前链顶的\(f\),然后跳到链顶的父亲,重复这个过程就可以了,更具体的可见代码的\(modify\)函数

复杂度\(O(n \log^2 n)\)

一个细节:矩阵乘法不具有交换律,注意是从下往上乘,按\(dfs\)序从大到小乘

PS.此题还有\(O(n \log n)\)\(LCT\)做法

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define MAXN 100005

typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
struct Matrix {
    LL data[2][2];
    Matrix() { memset(data, 0, sizeof data); }
    Matrix(LL a00, LL a01, LL a10, LL a11) { data[0][0] = a00, data[0][1] = a01, data[1][0] = a10, data[1][1] = a11; }
    static Matrix indentity() { return Matrix(1, 0, 0, 1); }
    Matrix operator *(const Matrix &) const;
};
struct SegmentTree {
    Matrix data[MAXN << 2];
    void modify(int, int, int, int, const Matrix &);
    void query(int, int, int, int, int, Matrix &);
};

char gc();
int read();
void dfs1(int);
void dfs2(int);
void modify(int, int);

int N, M, val[MAXN];
int idx, top[MAXN], bot[MAXN], dep[MAXN], fa[MAXN], dfn[MAXN], size[MAXN], heavy[MAXN];
LL f[MAXN][2], g[MAXN][2];
std::vector<int> trans[MAXN];
SegmentTree sgt;

int main() {
    //freopen("tmp.in", "r", stdin);
    //freopen("tmp.out", "w", stdout);
    
    N = read(), M = read();
    for (int i = 1; i <= N; ++i) val[i] = read();
    for (int i = 1; i < N; ++i) {
        int u = read(), v = read();
        trans[u].push_back(v);
        trans[v].push_back(u);
    }
    dfs1(1);
    top[1] = 1;
    dfs2(1);
    while (M--) {
        int x = read(), y = read();
        modify(x, y);
        printf("%lld\n", std::max(f[1][0], f[1][1]));
    }
    return 0;
}
inline char gc() {
    static char buf[1000000], *p1, *p2;
    if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin);
    return p1 == p2 ? EOF : *p2++;
}
inline int read() {
    int res = 0, op; char ch = gc();
    while (ch != '-' && (ch < '0' || ch > '9')) ch = gc();
    op = (ch == '-' ? ch = gc(), -1 : 1);
    while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc();
    return res * op;
}
void dfs1(int u) {
    dep[u] = dep[fa[u]] + 1;
    size[u] = 1;
    for (int i = 0; i < trans[u].size(); ++i) {
        int v = trans[u][i];
        if (v ^ fa[u]) {
            fa[v] = u, dfs1(v);
            size[u] += size[v];
            if (!heavy[u] || size[v] > size[heavy[u]]) heavy[u] = v;
        }
    }
}
void dfs2(int u) {
    dfn[u] = ++idx;
    g[u][0] = 0, g[u][1] = val[u];
    if (heavy[u]) {
        top[heavy[u]] = top[u];
        dfs2(heavy[u]);
        bot[u] = bot[heavy[u]];
    } else bot[u] = u;
    for (int i = 0; i < trans[u].size(); ++i) {
        int v = trans[u][i];
        if (v == fa[u] || v == heavy[u]) continue;
        top[v] = v, dfs2(v);
        g[u][0] += std::max(f[v][0], f[v][1]);
        g[u][1] += f[v][0];
    }
    f[u][0] = g[u][0] + std::max(f[heavy[u]][0], f[heavy[u]][1]);
    f[u][1] = g[u][1] + f[heavy[u]][0];
    sgt.modify(1, 1, N, dfn[u], Matrix(g[u][0], g[u][1], g[u][0], -INF));
}
Matrix Matrix::operator *(const Matrix &m) const {
    Matrix res;
    res.data[0][0] = std::max(data[0][0] + m.data[0][0], data[0][1] + m.data[1][0]);
    res.data[0][1] = std::max(data[0][0] + m.data[0][1], data[0][1] + m.data[1][1]);
    res.data[1][0] = std::max(data[1][0] + m.data[0][0], data[1][1] + m.data[1][0]);
    res.data[1][1] = std::max(data[1][0] + m.data[0][1], data[1][1] + m.data[1][1]);
    return res;
}
void SegmentTree::modify(int rt, int L, int R, int pos, const Matrix &m) {
    if (L == R) data[rt] = m;
    else {
        int mid = (L + R) >> 1;
        if (pos <= mid) modify(rt << 1, L, mid, pos, m);
        else modify(rt << 1 | 1, mid + 1, R, pos, m);
        data[rt] = data[rt << 1 | 1] * data[rt << 1];//注意乘的顺序 
    }
}
void SegmentTree::query(int rt, int L, int R, int l, int r, Matrix &res) {
    if (L >= l && R <= r) res = res * data[rt];//注意乘的顺序 
    else {
        int mid = (L + R) >> 1;
        if (r > mid) query(rt << 1 | 1, mid + 1, R, l, r, res);
        if (l <= mid) query(rt << 1, L, mid, l, r, res);
    }
}
void modify(int x, int y) {
    g[x][1] = g[x][1] - val[x] + y;
    val[x] = y;
    while (x) {
        int t = top[x], b = bot[x];
        sgt.modify(1, 1, N, dfn[x], Matrix(g[x][0], g[x][1], g[x][0], -INF));//在线段树中修改g 
        Matrix tmp;
        sgt.query(1, 1, N, dfn[t], dfn[b], tmp);//查出链顶f的新值 
        g[fa[t]][0] -= std::max(f[t][0], f[t][1]);//更新链顶父亲的g 
        g[fa[t]][1] -= f[t][0];
        f[t][0] = tmp.data[0][0], f[t][1] = tmp.data[0][1];//更新链顶 
        g[fa[t]][0] += std::max(f[t][0], f[t][1]);
        g[fa[t]][1] += f[t][0];
        x = fa[t];//跳到链顶的父亲 
    }
}
//Rhein_E

猜你喜欢

转载自www.cnblogs.com/Rhein-E/p/10670464.html
今日推荐