动态规划_树形DP

285. 没有上司的舞会

  • 状态表示:

    • \(dp[u][0]\)表示所有以\(u\)为根节点的子树中,不选\(u\)节点的所有方案
    • \(dp[u][1]\)表示所有以\(u\)为根节点的子树中,选择\(u\)节点的所有方案
  • 状态计算:

    • \(s\)\(u\)的儿子节点
    • \(dp[u][0] = \sum_{i = 0}^{m}{max(dp[s][0], dp[s][1])}\)
    • \(dp[u][1] = \sum_{i = 0}^{m} dp[s_i][0]\)
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 6010;
int e[N], ne[N], h[N], idx;
int dp[N][2], ha[N];
int n;
bool has_fa[N];

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

void dfs(int u) {
    dp[u][1] += ha[u];
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        
        dfs(j);
        
        dp[u][0] += max(dp[j][0], dp[j][1]);
        dp[u][1] += dp[j][0];
    }
}

int main() {
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> ha[i];
    
    memset(h, -1, sizeof h);
    for(int i = 1; i < n; i++) {
        int a, b; cin >> a >> b;
        add(b, a);
        has_fa[a] = true;
    }
    int p = 1;
    while(has_fa[p]) p++;
    dfs(p);
    cout << max(dp[p][0], dp[p][1]);
    return 0;
}

323. 战略游戏

  • \(dp[i][0]\):当前节点不选 -> 子节点必须选
  • \(dp[i][1]\):当前节点选 -> 子节点可选可不选

\(dp[u][0] += dp[j][1]\)
\(dp[u][1] += min(dp[j][0], dp[j][1])\)

皇宫看守

树的直径

  • 树的直径:任意两个叶子节点之间最长路径
    1. 任取一点作为起点,找到距离该店最远的一个点\(u\)
    2. 在找到距离\(u\)最远的一点\(v\)
    3. \(u、v\)之间的路径就是一条直径
  • 枚举根节点的所有儿子节点,找到到根节点前两大的儿子节点,距离当前根节点的最大距离就是找到的两个儿子节点到根节点之间的距离之和

树的最长路径

#include <iostream>
#include <cstring>

using namespace std;
const int N = 10010;
int e[N * 2], ne[N * 2], w[N * 2], h[N], idx;
int ans;
// 存储邻接表
void add(int a, int b, int c) {
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
// 记录当前节点的父亲节点
int dfs(int u, int fa) {
    int dist = 0, d1 = 0, d2 = 0;
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
       
        int d = dfs(j, u) + w[i];
        dist = max(dist, d);
        if(d >= d1) d2 = d1, d1 = d;
        else if(d > d2) d2 = d;
    }
    ans = max(ans, d1 + d2);
    return dist;
}

int main() {
    int n; cin >> n;
    
    memset(h, -1, sizeof h);
    
    for(int i = 1; i < n; i++) {
        int a, b, c; cin >> a >> b >> c;
        // 无向树
        add(a, b, c);
        add(b, a, c);
    }
    
    dfs(1, -1);
    
    cout << ans;
    
    return 0;
}

数字转换

  • 以每个数的约数之和作为父节点,该数作为儿子节点
  • 从子节点开始,找到树的直径

树的中心

  • 分别求每个点到其他节点的最远距离

  • 最长路径经过\(u\)节点

    1. 经过\(u\)节点向下走(与上一题类似)
    2. 经过\(u\)节点向上走,到达\(j\)节点:
      1. 向上走
      2. 向下走
        1. 没有经过\(u\):返回最大值
        2. 经过当前点:返回次大值
  • 用子节点信息更新父节点信息

  • 用父节点信息更新子节点信息


#include <iostream>
#include <cstring>

using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int e[M], ne[M], w[M], h[N], idx;
int d1[N], d2[N], p1[N], up[N];
bool is_leaf[N];

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

int dfs_d(int u, int fa) {
    d1[u] = d2[u] = -INF;
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        int d = dfs_d(j, u) + w[i];
        if(d >= d1[u]) {
            d2[u] = d1[u], d1[u] = d;
            p1[u] = j;
        }
        else if(d > d2[u]) d2[u] = d;
    }
    
    if(d1[u] == -INF) {
        d1[u] = d2[u] = 0;
        is_leaf[u] = true;
    }
    
    return d1[u];
}

void dfs_u(int u, int fa) {
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        if(p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];
        else up[j] = max(up[u], d1[u]) + w[i];
        dfs_u(j, u);
    }
}

int main() {
    int n; cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 1; i < n; i++) {
        int a, b, c; cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    dfs_d(1, -1);
    dfs_u(1, -1);
    
    int res =  d1[1];
    for(int i = 2; i <= n; i++) {
        if(is_leaf[i]) res = min(res, up[i]);
        else res = min(res, max(d1[i], up[i]));
    }
    cout << res << endl;
    
    return 0;
}

有依赖的背包问题

  • \(dp[u][j]\): 从以\(u\)为根的子树中选,总体积不超过\(j\)的方案
  • 每一颗子树看成一个物品组,每个物品组内部按照体积划分成\(m + 1\)种方案
#include <iostream>
#include <cstring>

using namespace std;
const int N = 110;
int h[N], e[N], ne[N], idx;
int n, m;
int dp[N][N];
int v[N], w[N];

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

void dfs(int u) {
    for(int i = h[u]; ~i; i = ne[i]) {
        int son = e[i];
        dfs(e[i]);
        // 枚举体积
        for(int j = m - v[u]; j >= 0; j--) {
            // 枚举决策
            for(int k = 0; k <= j; k++) {
                dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[son][k]);
            }
        }
    }    
    
    for (int i = m; i >= v[u]; i -- ) dp[u][i] = dp[u][i - v[u]] + w[u];
    for (int i = 0; i < v[u]; i ++ ) dp[u][i] = 0;
    
}
int main() {
    cin >> n >> m;
    int root;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; i++) {
        int p;
        cin >> v[i] >> w[i] >> p;
        if(p == -1) root = i;
        else add(p, i);
    }
    dfs(root);
    cout << dp[root][m];
    return 0;
}

1074. 二叉苹果树

#include <iostream>
#include <cstring>

using namespace std;
const int N = 110, M = N * 2;
int e[M], ne[M], w[M], h[N], idx;
int dp[M][M];

int n, m;

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

void dfs(int u, int fa) {
    for(int i = h[u]; ~i; i = ne[i]) {
        int vex = e[i];
        if(vex == fa) continue;
        dfs(vex, u);
        
        for(int j = m; j; j--) 
            for(int k = 0; k < j; k++)
                dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[vex][k] + w[i]);
    }
}
int main() {
    cin >> n >> m;

    memset(h, -1, sizeof h);
    for(int i = 1; i < n; i++) {
        int a, b, c; cin >> a >> b >> c;
        add(a, b, c); add(b, a, c);
    }
    
    dfs(1, -1);
    
    cout << dp[1][m] << endl;
    
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Hot-machine/p/13377488.html
今日推荐