[洛谷4643]【模板】动态dp 题解

[洛谷4643]【模板】动态dp 题解

题目地址:洛谷:【P4643】【模板】动态dp – 洛谷

题目描述

给定一棵 n 个点的树,点带点权。
有 m 次操作,每次操作给定 x, y ,表示修改点 x 的权值为 y 。
你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

输入输出格式

输入格式:
第一行, n, m ,分别代表点数和操作数。
第二行, V1, V2, …, Vn ,代表 n 个点的权值。
接下来 n-1 行, x, y ,描述这棵树的 n-1 条边。
接下来 m 行, x, y ,修改点 x 的权值为 y 。

输出格式:
对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。
保证答案在 int 范围内

输入输出样例

输入样例#1:

10 10
-11 80 -99 -76 56 38 92 -51 -34 47 
2 1
3 1
4 3
5 2
6 2
7 1
8 2
9 4
10 7
9 -44
2 -17
2 98
7 -58
8 48
3 99
8 -61
9 76
9 14
10 93

输出样例#1:

186
186
190
145
189
288
244
320
258
304

说明

对于30%的数据, 1≤n,m≤10
对于60%的数据, 1≤n,m≤1000
对于100%的数据, 1≤n,m≤10^5

题解

参考资料:Luogu P4643 【模板】动态dp – 胡小兔 – 博客园
首先这个题不带修改就是一个“没有上司的舞会”模型,用$dp[u][0/1]$代表$u$这个点选或不选其子树中的最大权独立集的权值和,DFS向上转移即可。
现在我们需要动态地维护树上的DP值,我们需要一种方法来使权值能快速更新。在这道题里,我们的转移方程是
$$ \begin{aligned}
dp[u][0] &= \sum_{v \text{ is a son of } u} \max \{ dp[v][0], dp[v][1] \} \\
dp[u][1] &= \sum_{v \text{ is a son of } u} dp[v][0]
\end{aligned} $$
首先不好搞的是儿子的数量比较多求和不好做,我们先考虑把树给剖掉,使用$g[u][0/1]$记录$u$这个点选或不选其自身和所有轻儿子的答案的和,而$f[u][0/1]$则记录总的答案,即合并轻重儿子考虑。现在,方程变成了这样
$$ \begin{aligned}
f[u][0] &= g[u][0] + \max \{ f[v][0], f[v][1] \} \\
f[u][1] &= g[u][1] + f[v][0]
\end{aligned} $$
其中的$v$是$u$的重儿子。到现在为止,似乎也没有把转移的形式变得可以树上操作,而且怎么求$g[u][0/1]$的值也不知道。这时,我们想到了矩阵。
矩阵的乘法可以理解为向量运算,即$\mathbf{A} \times \mathbf{B}$可以理解为$\mathbf{B}$中的每一个行向量$\mathbf{b_1}$乘上$\mathbf{A}$中每一行对应的系数$\{ a_{1, 1}, a_{1, 2}, \dots, a_{1, n} \}$再加起来得到的行向量组(或者也可以用列向量的形式理解)。
在这里,我们需要重新定义向量加法和标量乘法。
$$ \begin{aligned}
\mathbf{a} + \mathbf{b} = \mathbf{c} &\Rightarrow c_i = \max \{ a_i, b_i \} \\
\mathbf{a} \times b = \mathbf{c} &\Rightarrow c_i = a_i + b
\end{aligned} $$
我们发现上面的重新定义的运算具有很好的性质,可以按照这样的定义构造向量空间,来定义新的矩阵乘法。新的矩阵乘法如下
$$ \mathbf{A} \times \mathbf{B} = \mathbf{C} \Rightarrow C_{i, j} = \max_k \{ A_{i, k} + B_{k, j} \} $$
这样,我们就可以构造一个矩阵来进行DP的转移了
$$ \begin{bmatrix} f[u][0] \\ f[u][1] \end{bmatrix} = \begin{bmatrix} f[v][0] \\ f[v][1] \end{bmatrix} \times \begin{bmatrix} g[u][0] & g[u][0] \\ g[u][1] & 0 \end{bmatrix} $$
那么,只要我们知道了整棵树上的转移矩阵(即右侧的那个$2 \times 2$矩阵),就可以通过线段树维护重链矩阵乘法,从而快速求某条重链顶端的答案了。我们无需设置一个列向量来乘上这个转移矩阵,这是因为,叶子节点的$f[u][0]=g[u][0]=0, f[u][1]=g[u][1]=w_u$,看上去就跟列向量一样了。
接下来考虑修改问题,我们考虑更新这个结点的矩阵后,沿重链上跳,每个重点的顶的父亲都会受到影响,此时只需要把影响大力算出来改一下就好。
我的初始化是逐个点改权值,复杂度是$O(n \log^2 n)$的,实际上可以先$O(n)$地做一遍DP,然后用DP值构造矩阵,这个复杂度是$O(n+n \log n)$,相比而言常数更优秀,不过代码更长,考虑到这个题没卡常,就懒得写了233
总复杂度是$O((n+m) \log^2 n)$。

代码

// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
        ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1; register char c = fgc();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

struct Matrix {
    LL m[2][2];
    Matrix() {
        memset(m, 0, sizeof(m));
    }
    inline LL* operator[](const int &x) const {
        return const_cast<LL*>(m[x]);
    }
    inline Matrix operator*(const Matrix &rhs) const {
        Matrix res;
        for(int i = 0; i <= 1; i++) {
            for(int j = 0; j <= 1; j++) {
                for(int k = 0; k <= 1; k++) {
                    res[i][j] = std::max(res[i][j], m[i][k] + rhs[k][j]);
                }
            }
        }
        return res;
    }
    inline Matrix& operator*=(Matrix &rhs) {
        return *this = *this * rhs;
    }
};

int n, m, w[MAXN];
std::vector<int> gra[MAXN];

int fa[MAXN], siz[MAXN], dep[MAXN], son[MAXN], top[MAXN], end[MAXN], dfn[MAXN], vn[MAXN], clk;

void dfs1(int u) {
    siz[u] = 1;
    for(auto v : gra[u]) {
        if(v == fa[u]) continue;
        fa[v] = u; dep[v] = dep[u] + 1;
        dfs1(v);
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int tp) {
    dfn[u] = ++clk; vn[dfn[u]] = u; top[u] = tp; 
    end[tp] = std::max(end[tp], dfn[u]);
    if(son[u]) {
        dfs2(son[u], tp);
    }
    for(auto v : gra[u]) {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

Matrix tr[MAXN << 2], val[MAXN];

#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)

void modify(int o, int l, int r, int x) {
    if(l == r) {
        tr[o] = val[vn[l]]; return;
    }
    if(x <= mid) modify(lch, l, mid, x);
    else modify(rch, mid + 1, r, x);
    tr[o] = tr[lch] * tr[rch];
}

Matrix query(int o, int l, int r, int ll, int rr) {
    if(l == ll && r == rr) return tr[o];
    if(rr <= mid) return query(lch, l, mid, ll, rr);
    else if(ll > mid) return query(rch, mid + 1, r, ll, rr);
    else return query(lch, l, mid, ll, mid) * query(rch, mid + 1, r, mid + 1, rr);
}

inline Matrix query(int u) {
    return query(1, 1, n, dfn[top[u]], end[top[u]]);
}

inline void modify(int u, int v) {
    val[u][1][0] += v - w[u];
    w[u] = v;
    Matrix m1, m2;
    while(u) {
        m1 = query(top[u]);
        modify(1, 1, n, dfn[u]);
        m2 = query(top[u]);
        u = fa[top[u]];
        val[u][0][0] += std::max(m2[0][0], m2[1][0]) - std::max(m1[0][0], m1[1][0]);
        val[u][0][1] = val[u][0][0];
        val[u][1][0] += m2[0][0] - m1[0][0];
    }
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        w[i] = readint();
    }
    for(int i = 1, u, v; i < n; i++) {
        u = readint(); v = readint();
        gra[u].push_back(v); gra[v].push_back(u);
    }
    dfs1(1);
    dfs2(1, 1);
    for(int i = 1; i <= n; i++) {
        int t = w[i]; w[i] = 0;
        modify(i, t);
    }
    while(m--) {
        int x = readint(), y = readint();
        modify(x, y);
        Matrix res = query(1);
        printf("%lld\n", std::max(res[0][0], res[1][0]));
    }
    return 0;
}


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据