动态dp

引入

洪水

小 A 走到一个山脚下,准备给自己造一个小屋。这时候,小 A 的朋友(op,又叫管理员)打开了创造模式,然后飞到山顶放了格水。于是小 A 面前出现了一个瀑布。作为平民的小 A 只好老实巴交地爬山堵水。那么问题来了:

我们把这个瀑布看成是一个 \(n\) 个节点的有根树,编号为 \(1\)\(n\) 的整数,\(1\) 号点为根。每个节点有权值(爬上去的代价)。小 A 要选择一些节点,以其权值和作为代价将这些点删除(堵上),使得根节点与所有叶子结点不连通。问最小代价。不过到这还没结束。小 A 的朋友觉得这样子太便宜小 A 了,于是他还会不断地修改地形,使得某个节点的权值发生变化。不过到这还没结束。

小 A 觉得朋友做得太绝了,于是放弃了分离所有叶子节点的方案。取而代之的是,每次他只要在某个子树中(和子树之外的点完全无关)。于是他找到你。

朴素做法

\(f_u\)\(u\) 子树的答案

\(f_u = \min(w_u, \sum_v f_v)\)

这样会 \(TLE\)

动态dp

我们对这棵树进行 重链剖分

\(g_u\) 表示只考虑 \(u\) 的所有轻儿子的答案和。

\(f_u = \min(w_u, f_{son_u} + g_u)\)

定义广义矩阵乘法,\(C = A * B\),则

\[C_{i, j} = min_{k} (A_{i, k} + B_{k, j}) \]

我们要找到转移矩阵,满足

\(U* \begin{pmatrix}f_{son_{u}} \\ 0\end{pmatrix} = \begin{pmatrix}f_{u} \\ 0\end{pmatrix}\)

可以得到:

\[U =\begin{pmatrix}g_u & w_u\\ inf & 0 \end{pmatrix} \]

每次只有在切换重链的时候会改变一个点的矩阵。

因为这个矩阵满足结合律,所以一条重链上的的 \(U\) 的乘积可以用线段树计算。

所以时间复杂度为 \(O(n \log n^2)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10, inf = 1e15 + 10;
struct Mat {
    int a[2][2];
    Mat() { a[0][0] = a[0][1] = a[1][1] = a[1][0] = inf; }
    Mat operator*(const Mat& T) const {
        Mat res;
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 2; k++) {
                    res.a[i][j] = min(res.a[i][j], a[i][k] + T.a[k][j]);
                }
            }
        }
        return res;
    }
};
Mat g[N];
int n;
vector<int> edge[N];
int val[N];
int sze[N], f[N], dep[N], fa[N], son[N], top[N], End[N], dfn[N], in[N], tot;

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

void dfs2(int u, int tp) {
    dfn[u] = ++tot, in[tot] = u;
    top[u] = tp, End[tp] = max(End[tp], tot);
    g[u].a[0][0] = 0, g[u].a[0][1] = val[u];
    g[u].a[1][0] = inf, g[u].a[1][1] = 0;
    if (son[u]) {
        dfs2(son[u], tp);
        f[u] += f[son[u]];
    } else
        g[u].a[0][0] = inf;
    for (auto v : edge[u]) {
        if (v == fa[u] || v == son[u])
            continue;
        dfs2(v, v);
        f[u] += f[v];
        g[u].a[0][0] += f[v];
    }
    if (son[u])
        f[u] = min(f[u], val[u]);
    else
        f[u] = val[u];
    // cout << u << " " << f[u] << "\n";
}

Mat t[N * 4];

void build(int p, int l, int r) {
    if (l == r) {
        t[p] = g[in[l]];
        return;
    }
    int mid = (l + r) / 2;
    build(p * 2, l, mid), build(p * 2 + 1, mid + 1, r);
    t[p] = t[p * 2] * t[p * 2 + 1];
}

void change(int p, int L, int R, int x) {
    if (L > x || R < x)
        return;
    if (L == R) {
        t[p] = g[in[L]];
        return;
    }
    int mid = (L + R) / 2;
    change(p * 2, L, mid, x), change(p * 2 + 1, mid + 1, R, x);
    t[p] = t[p * 2] * t[p * 2 + 1];
}

Mat query(int p, int L, int R, int l, int r) {
    if (l <= L && R <= r) {
        return t[p];
    }
    int mid = (L + R) / 2;
    if (l <= mid && mid < r)
        return query(p * 2, L, mid, l, r) * query(p * 2 + 1, mid + 1, R, l, r);
    if (l <= mid)
        return query(p * 2, L, mid, l, r);
    return query(p * 2 + 1, mid + 1, R, l, r);
}

void upd(int x, int v) {
    val[x] += v;
    g[x].a[0][1] = val[x];
    Mat bef, aft;
    while (x) {
        bef = query(1, 1, n, dfn[top[x]], End[top[x]]);
        change(1, 1, n, dfn[x]);
        aft = query(1, 1, n, dfn[top[x]], End[top[x]]);
        x = fa[top[x]];
        g[x].a[0][0] = g[x].a[0][0] - min(bef.a[0][0], bef.a[0][1]) + min(aft.a[0][0], aft.a[0][1]);
    }
}

int qry(int u) {
    Mat res = query(1, 1, n, dfn[u], End[u]);
    return min(res.a[0][0], res.a[0][1]);
}

signed main() {
	cin.tie(0), cout.tie(0);
	ios::sync_with_stdio(0); 
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> val[i];
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    dfs1(1, 0), dfs2(1, 1);
    for (int i = 1; i <= n; i++) {
        End[i] = End[top[i]];
    }
    build(1, 1, n);
    int T;
    cin >> T;
    while (T--) {
        char op;
        int x, y;
        cin >> op >> x;
        if (op == 'C') {
            cin >> y;
            upd(x, y);
        } else
            cout << qry(x) << "\n";
    }
    return 0;
}
posted @ 2025-09-06 21:15  merlinkkk  阅读(9)  评论(0)    收藏  举报