树形DP

普通树形DP

由于树固有的递归性质,树形 DP 一般都是递归进行的。
——通常用dp[i]表示为以节点i为根的子树规划得到的答案;

例题

https://www.luogu.com.cn/problem/P1352
本题dp[i][f]代表根为i的子树的点的状态0/1,0是不在,1是存在

Code

点击查看代码
const int maxn = 6e3 + 10;
int n, r[maxn];
int dp[maxn][2];
vector<int> ve[maxn];
int in[maxn];

int dfs(int u, int f) {
    if (dp[u][f]) { return dp[u][f]; }
    if (f) {
        int sum = 0;
        for (auto v : ve[u]) {
            sum += dfs(v, 0);
        }
        dp[u][f] = max(dp[u][f], sum + r[u]);
    }
    else {
        int sum = 0;
        for (auto v : ve[u]) {
            sum += max(dfs(v, 0), dfs(v, 1));
        }
        dp[u][f] = max(dp[u][f], sum);
    }
    return dp[u][f];
}
void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> r[i];
    }
    int root;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        ve[v].push_back(u);
        in[u]++;
    }
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {
            root = i;
            break;
        }
    }
    cout << max(dfs(root, 1), dfs(root, 0));
}

树上背包

背包问题与树形问题的结合,即在树上进行背包dp
方法与之类似

重点

在背包时要用tmp来替换dp,防止在转移时错误调用。
https://www.luogu.com.cn/problem/P2014
dp[i][j]表示根为i的子树的背包容量为j时最多装下的价值
状态转移:
for (int j = m + 1; j >= 1; j--) {代表u的背包容量,j>=1是因为u必须装着,要占用一个空间
for (int k = 0; k < j; k++) {代表u的儿子v的背包容量,k<j是因为儿子不能占用全部空间
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
}
}

Code

点击查看代码
const int maxn = 1e5 + 10;

int n, m, s[maxn];
vector<int> ve[310];
int dp[310][310];
void dfs(int u) {
    dp[u][1] = s[u];
    for (auto v : ve[u]) {
        dfs(v);
        for (int j = m + 1; j >= 1; j--) {
            for (int k = 0; k < j; k++) {
                dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
            }
        }
    }
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        int k;
        cin >> k >> s[i];
        ve[k].push_back(i);
    }
    dfs(0);
    cout << dp[0][m + 1];
}

换根DP

树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响;

重点

常用两遍dfs,第一次强令一个根预处理,第二次从第一个根往下替换,动态规划;
第二次dfs要时刻注意根与儿子的关系;
以及在换根时节点信息的变化,该更新的要更新;
防止用错误的数据得到错误的答案

例题

https://www.luogu.com.cn/problem/P3478
从根u换成儿子v变根,以v为子树的点深度-1,其他点+1;

Code

点击查看代码
const int maxn = 1e6 + 10;
int read();

int n;
vector<int> ve[maxn];
int sz[maxn];
int ans[maxn];
void dfs(int u, int fa) {
    sz[u] = 1;
    for (auto v : ve[u]) {
        if (v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
    }
    ans[1] += sz[u];
}
void dfs1(int u, int fa) {
    for (auto v : ve[u]) {
        if (v == fa) continue;
        ans[v] = ans[u] + n - sz[v] * 2;
        dfs1(v, u);
    }
}
void solve() {
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        ve[u].pb(v);
        ve[v].pb(u);
    }
    dfs(1, 0);
    dfs1(1, 0);
    int res = 0, mark = 0;
    for (int i = 1; i <= n; i++) {
        if (ans[i] > res) {
            res  = ans[i];
            mark = i;
        }
    }
    cout << mark;
}
posted @ 2024-07-10 20:17  uanQ  阅读(5)  评论(0编辑  收藏  举报