Files
python/TangDou/AcWing/DP/Tree/树形DP【专题】.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

4.8 KiB
Raw Blame History

##树形$DP$专题

【$T1$】求一颗树的直径

难度 ★ AcWing 1072. 树的最长路径

难度 ★★ AcWing 1073. 树的中心

AcWing 1075. 数字转换

法一:$dfs$来回两遍

从任意一点出发找最长$A$,再从$A$找最长;和树形$DP$没有一丁点关系 缺点:不能处理负权边

void dfs1(int u, int sum) {
    st[u] = 1;
    if (sum > ans) {
        ans = sum; // 记录最大距离
        t1 = u;    // 记录最远的点t1
    }
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (st[j]) continue;
        dfs1(j, sum + w[i]);
    }
}

void dfs2(int u, int sum) {
    st[u] = 1;
    if (sum > ans) {
        ans = sum; // 记录最大距离
        t2 = u;    // 记录最远的点t2
    }
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (st[j]) continue;
        dfs2(j, sum + w[i]);
    }
}

...
dfs1(1, 0); // 先找到点距离点1最远的点t
...
dfs2(t1, 0); // 找到距离点t最远的点

法二:树形DP

$d1[u]$表示到$u$的最长路径。$d2[u]$表示到$u$的第二长路径,那么易得 $d1[u]+ d2[u]$即为经过$u$的最长路径

void dfs(int u) {
    st[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (st[j]) continue;
        // 走j子树
        dfs(j);

        // d1[u]:最长路径,d2[u]:次长路径
        if (d1[j] + w[i] >= d1[u])
            d2[u] = d1[u], d1[u] = d1[j] + w[i]; // 最长路转移
        else if (d1[j] + w[i] > d2[u])
            d2[u] = d1[j] + w[i]; // 次长路转移
    }
    // 更新结果
    ans = max(ans, d1[u] + d2[u]);
}

###【$T2$】覆盖一棵树的最大、最小权值 难度 ★ AcWing 323 战略游戏

难度★ AcWing 285. 没有上司的舞会

对比: 没有上司的舞会:每条边上 最多 选择一个点,求最大权值 战略游戏:每条边上 最少 选择一条点,求最小权值

难度★★★ AcWing 1077. 皇宫看守

###【$T3$】保留一定数量的点、边,求方案数

AcWing 1074 . 二叉苹果树

计蒜客 $Mila$的苹果树 【本质是就是二叉苹果树的变形】

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 2100, M = N << 1;
const int MOD = 998244353;

int e[M], h[N], idx, ne[M];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int n;
int limit[N]; // 以u为根的子树中数量上限
int st[N];    // 是不是访问过

LL f[N][N];

void dfs(int u) {
    st[u] = 1;                 // 访问过了
    f[u][0] = 1;               // u子树中,放0个苹果的方案是1,此时只能是不管有多少个子结点,都是啥也不能放,方案唯一
    if (limit[u]) f[u][1] = 1; // 在u子树中,如果只放1个的话,那么必然是放在u节点上,否则u子树就不存在的,此时方案数是1

    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        dfs(v);

        // 分组背包
        for (int j = limit[u]; j >= 0; j--) // 枚举u子树体积,倒序降维
            // Q:为什么这里k=0开始是错的呢?
            for (int k = 1; k <= min(j, limit[v]); k++) // 枚举决策,v子树中放资源k个
                f[u][j] = (f[u][j] + f[v][k] * f[u][j - k]) % MOD;
    }
}
int main() {
    freopen("appletree.in", "r", stdin);
    freopen("appletree.out", "w", stdout);

    memset(h, -1, sizeof h);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> limit[i];

    for (int i = 1; i < n; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a); // 无向图
    }

    dfs(1);

    int res = 0;
    for (int i = 0; i <= limit[1]; i++) res = (res + f[1][i]) % MOD;

    printf("%d\n", res);
    return 0;
}

51nod 1588 幸运树 【树形$dp$统计树上方案数】

P4084 [$USACO17DEC$]Barn Painting $G$

P3349 [$ZJOI2016$]小星星 TODO

https://blog.csdn.net/A_Bright_CH/article/details/83443279