动态规划-路径相关树形DP and 换根DP

路径相关的树形动态规划(Tree DP)是一种在树型结构上进行动态规划的方法。它主要解决的问题是在给定的树中,求解与路径有关的动态规划问题。

在树形结构中,每个节点通常具有子节点和父节点,形成了一种层次结构。在路径相关的树形动态规划中,我们需要考虑从根节点到叶子节点的路径,并根据问题的要求计算相关的值。

树形DP通常通过遍历树的方式进行计算,可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来完成。在计算过程中,我们可以利用子节点的计算结果来更新父节点的值,直到最终计算出整棵树的结果。

具体而言,路径相关的树形动态规划可以用来解决诸如最长路径、最短路径、路径上的最大和或最小值等问题。通过定义适当的状态和状态转移方程,我们可以使用树形DP来高效地解决这些问题。

换根DP是一种树形动态规划的技巧,用于在树结构上解决动态规划问题。在实际问题中,我们通常会面临需要在同一棵树上不同的根节点下进行动态规划计算的情况。为了解决这种问题,我们引入了换根DP 技巧。

在换根DP中,我们首先任选一棵树中的一个节点作为新的根节点,然后根据原树的边和节点关系,重新组织形成以该节点为根的树。接着,我们可以利用树形动态规划的方法,在新的根节点下进行动态规划计算。通过这种方法,我们可以获得以新根节点为根的子树的动态规划结果。

然后,对于每个节点,我们可以根据其与原根节点的关系,将以新根节点为根的子树的结果转化为以原根节点为根的子树的结果。这样,我们就可以在原树上不同的根节点下进行动态规划计算。

通过换根DP技巧,我们可以在同一棵树上不同的根节点下进行动态规划计算,并获取到所有可能的结果。这在很多树形动态规划的问题中都是非常有用的技巧,可以帮助我们高效地解决相关问题。

路径相关树形DP大致模板:

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1000;  // 树节点的最大数量
vector<int> adjacency_list[MAXN];  // 树的邻接表表示
int dp[MAXN];  // 保存中间结果的数组

void dfs(int u, int parent) {
    // 计算节点u的状态值或其他需要的信息
    // 更新dp数组或其他中间结果

    for (int v : adjacency_list[u]) {
        if (v != parent) {
            dfs(v, u);  // 递归遍历邻接节点v

            // 根据具体问题的要求,更新节点u的状态值或其他信息
        }
    }
}

int tree_dp(int root) {
    dfs(root, -1);  // 从根节点开始进行深度优先搜索

    // 返回最终结果,例如dp[root]或其他计算结果
    return dp[root];
}

int main() {
    int n;  // 树的节点数
    cin >> n;

    for (int i = 0; i < n-1; ++i) {
        int u, v;  // 树的边
        cin >> u >> v;
        adjacency_list[u].push_back(v);
        adjacency_list[v].push_back(u);
    }

    int root = 0;  // 选择树的根节点
    int result = tree_dp(root);
    cout << "Result: " << result << endl;

    return 0;
}

换根DP大致模板:

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1000;  // 树节点的最大数量
vector<int> adjacency_list[MAXN];  // 树的邻接表表示
int dp[MAXN];  // 保存中间结果的数组
int sub_dp[MAXN];  // 每个节点的子树动态规划结果

void dfs(int u, int parent) {
    // 计算节点u的状态值或其他需要的信息
    // 更新dp数组或其他中间结果
    // 更新sub_dp数组

    for (int v : adjacency_list[u]) {
        if (v != parent) {
            dfs(v, u);  // 递归遍历邻接节点v

            // 根据具体问题的要求,更新节点u的状态值或其他信息
            // 更新sub_dp数组
        }
    }
}

void tree_dp(int root) {
    dfs(root, -1);  // 从根节点开始进行深度优先搜索

    // 对每个节点进行处理,将以新根节点为根的子树的结果转化为以原根节点为根的子树的结果
}

int main() {
    int n;  // 树的节点数
    cin >> n;

    for (int i = 0; i < n-1; ++i) {
        int u, v;  // 树的边
        cin >> u >> v;
        adjacency_list[u].push_back(v);
        adjacency_list[v].push_back(u);
    }

    for (int i = 0; i < n; ++i) {
        tree_dp(i);  // 以每个节点作为新的根节点进行处理
    }

    // 输出最终结果
    return 0;
}

我们各通过一道例题来了解这两个DP形式。

首先是路径相关树形DP,最经典的一个问题就是解决"树的直径"问题,具体为给定一棵无向树,每条边上都有一个权值。现在要求找到树中的一条路径,使得该路径上各边的权值之和最大。输出这个最大权值之和。

这个问题可以使用路径相关树形动态规划来解决。通常的解决方法是使用两次深度优先搜索(DFS)。首先,第一次DFS用于找到树的直径,然后第二次DFS用于计算路径上边权值之和的最大值。

基本思路如下:

  1. 使用第一次DFS找到一棵树的直径,记录直径上的两个端点为节点 A 和节点 B;
  2. 使用第二次DFS,从节点 A 出发,找到离节点 A 最远的节点,记为节点 C;
  3. 使用第二次DFS,从节点 C 出发,找到离节点 C 最远的节点,记为节点 D;
  4. 节点 C 和节点 D 之间的路径就是树的直径,计算这条路径上边权值之和。

代码如下:

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1000; // 树节点的最大数量
vector<pair<int, int>> adjacency_list[MAXN]; // 树的邻接表表示
int dp[MAXN]; // 保存中间结果的数组
int max_weight; // 存储最大权值之和

void dfs(int u, int parent) {
    dp[u] = 0;
    for (const auto& edge : adjacency_list[u]) {
        int v = edge.first;
        int weight = edge.second;
        if (v != parent) {
            dfs(v, u);
            dp[u] = max(dp[u], dp[v] + weight);
        }
    }
}

void find_diameter(int u, int parent) {
    int max_child_dp = 0;
    int max_child_v = -1;
    for (const auto& edge : adjacency_list[u]) {
        int v = edge.first;
        int weight = edge.second;
        if (v != parent && dp[v] + weight > max_child_dp) {
            max_child_dp = dp[v] + weight;
            max_child_v = v;
        }
    }

    if (max_child_v != -1) {
        dfs(max_child_v, u);
        find_diameter(max_child_v, u);
    }
}

int main() {
    int n; // 树的节点数
    cin >> n;
    
    for (int i = 0; i < n - 1; ++i) {
        int u, v, weight; // 树的边及对应的权值
        cin >> u >> v >> weight;
        adjacency_list[u].push_back({v, weight});
        adjacency_list[v].push_back({u, weight});
    }
    
    // 第一次DFS:计算每个节点作为路径的终点能获得的最大权值之和
    dfs(1, -1);
    
    int root = 1;
    for (int i = 2; i <= n; ++i) {
        if (dp[i] > dp[root]) {
            root = i;
        }
    }
    
    // 第二次DFS:根据计算的结果,找到树的直径路径
    max_weight = 0;
    find_diameter(root, -1);
    
    cout << "Max Weight: " << max_weight << endl;
    
    return 0;
}

接下来是换根DP,一个典型的换根DP的题目是「树的同构计数」。题目为:给定一棵树,每个节点上有一个颜色。定义一棵树的同构子树是指具有相同结构且节点颜色吻合的子树。现在需要计算不同的同构子树数量。

解决这个问题的一种常见方法是使用换根DP。对于一棵树,我们可以选择任意一个节点作为根节点,然后通过动态规划算法进行计算,从而解决这个问题。

具体的解决方法可以按照以下步骤进行:

  1. 从任意节点开始,采用深度优先搜索(DFS)方式遍历整棵树,对每个节点进行换根DP的计算;
  2. 对于每个节点,我们需要计算以当前节点为根的子树,具有不同颜色的同构子树数量;
  3. 在递归计算每个节点的换根DP时,需要记录下以当前节点为根的子树的结构,可以使用哈希表等方式进行记录;
  4. 最后统计各个节点的换根DP结果并求和,即是整棵树的同构子树数量。

代码如下:

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

const int MAXN = 1005; // 树节点的最大数量
vector<int> tree[MAXN]; // 树的邻接表表示
int color[MAXN]; // 存储每个节点的颜色
unordered_map<vector<int>, int> subtree_count[MAXN]; // 存储每个节点以其为根的子树的同构子树数量

// 求解以当前节点为根的子树的哈希值
vector<int> calculate_hash(int u, int parent) {
    vector<int> hash;
    for (int v : tree[u]) {
        if (v != parent) {
            hash.push_back(calculate_hash(v, u));
        }
    }
    sort(hash.begin(), hash.end());
    return hash;
}

// 递归地计算换根DP
void calculate_subtree_count(int u, int parent) {
    vector<int> hash;
    for (int v : tree[u]) {
        if (v != parent) {
            calculate_subtree_count(v, u);
            hash.push_back(subtree_count[v][calculate_hash(v, u)]);
        }
    }
    sort(hash.begin(), hash.end());
    subtree_count[u][hash]++; // 更新当前节点子树的同构子树数量
}

int main() {
    int n; // 树的节点数
    cin >> n;

    for (int i = 1; i <= n; ++i) {
        cin >> color[i]; // 读入每个节点的颜色
    }

    for (int i = 0; i < n - 1; ++i) {
        int u, v; // 树的边
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }

    calculate_subtree_count(1, -1); // 从节点1开始递归计算换根DP

    int total_count = 0;
    for (const auto& it : subtree_count[1]) {
        total_count += it.second;
    }

    cout << "Total distinct isomorphic subtrees: " << total_count << endl;

    return 0;
}

讲完这两个DP形式,接下来,就是动态规划应用较为广泛以及重要的背包问题了,敬请期待!
 

  • 35
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值