You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##树形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