首页 > 其他分享 >树形DP——AcWing 285. 没有上司的舞会

树形DP——AcWing 285. 没有上司的舞会

时间:2024-06-21 11:00:18浏览次数:27  
标签:int adj dfs DP happy 285 节点 dp AcWing

目录

树形DP

定义

运用情况

注意事项

解题思路

AcWing 285. 没有上司的舞会 

题目描述

运行代码

代码思路

改进思路

改进代码(AI)

其它代码

代码思路

树形DP

定义

  • 树形 DP 是在树上进行的动态规划。
  • 它利用树的结构特点,通过递归或迭代的方式,在每个节点上进行状态计算和转移,以求解最优解。

运用情况

  • 树的最长路径问题:找到树中一条路径,使得路径上所有边的权值之和最大。
  • 数字转换问题:在给定的数字范围内,通过数字变换找到最长的变换步数。
  • 树的中心问题:找到树中到其他节点最远距离最近的点。
  • 没有上司的舞会问题:在满足条件的前提下,选择部分职员参加舞会,使得快乐指数总和最大。

注意事项

  • 状态表示:选择合适的状态表示方式,能够简洁地描述问题的本质。
  • 状态转移:确定状态之间的转移关系,确保能够正确地计算最优解。
  • 初始化:根据问题的要求,对状态进行合理的初始化。
  • 边界情况:处理好树的边界节点,确保算法的正确性。
  • 优化:根据具体问题,考虑使用合适的优化技巧,如记忆化搜索、剪枝等。

解题思路

  • 确定问题的状态:根据问题的要求,确定每个节点的状态。
  • 建立状态转移方程:根据树的结构和问题的约束条件,建立状态之间的转移关系。
  • 确定边界情况:处理好树的叶子节点或特殊情况的状态。
  • 选择合适的遍历顺序:根据问题的特点,选择合适的遍历树的顺序,如前序遍历、后序遍历或层次遍历。
  • 计算最优解:通过遍历树,根据状态转移方程计算每个节点的最优解,并最终得到整个树的最优解。

AcWing 285. 没有上司的舞会 

题目描述

285. 没有上司的舞会 - AcWing题库

运行代码

#include <iostream>
#include <vector>
using namespace std;
const int N = 6001;
vector<int> adj[N];
int happy[N];
int dp[N][2]; 
void dfs(int u, int par) {
    dp[u][1] = happy[u];
    dp[u][0] = 0;
    for (int v : adj[u]) {
        if (v!= par) {
            dfs(v, u);
            dp[u][1] += dp[v][0];
            dp[u][0] += max(dp[v][0], dp[v][1]);
        }
    }
}
int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> happy[i];
    for (int i = 1; i < n; i++) {
        int l, k;
        cin >> l >> k;
        adj[k].push_back(l);
        adj[l].push_back(k);
    }
    dfs(1, -1);
    cout << max(dp[1][0], dp[1][1]) << endl;
    return 0;
}

代码思路

  1. 数据结构定义:

    • 定义一个邻接列表adj[N]来存储树的结构,其中adj[u]存放节点u的所有子节点。
    • happy[N]数组存储每个节点的幸福值。
    • dp[N][2]是一个二维动态规划数组,其中dp[u][0]表示以节点u为根的子树中,不包含节点u的最大幸福值路径和;dp[u][1]表示以节点u为根的子树中,包含节点u的最大幸福值路径和。
  2. DFS(深度优先搜索):

    • 函数dfs(u, par)遍历以u为根的子树。par表示u的父节点,用于避免回头访问。
    • 对于u的每个子节点v(不包括父节点par),递归调用dfs(v, u)
    • 更新dp[u][1]为当前节点的幸福值加上所有不包含当前节点的子树路径和的最大值(即dp[v][0])。
    • 更新dp[u][0]为所有子树路径和的最大值,可以选择包含或不包含当前子树的根节点(即max(dp[v][0], dp[v][1]))。
  3. 计算结果:

    • 在遍历完成后,根节点1dp[1][0]dp[1][1]中较大者即为所求的最大幸福值路径和。

改进思路

  • 常量优化N = 6001是一个相对较大的常量,如果题目保证了节点数的范围,可以适当减小这个值来节省空间。
  • 输入验证:实际应用中,添加对输入数据的验证,如检查节点数是否在合理范围内,边的定义是否构成一棵有效的树等,可以增强程序的健壮性。
  • 内存使用:如果树的深度非常大,可以考虑使用迭代法(如栈)来替代递归,减少调用栈的使用,避免栈溢出。
  • 添加注释:为了提高代码的可读性,添加必要的注释来解释每个主要步骤的目的。
  • 明确常量意义:虽然在这个上下文中N的意义相对明确,但给常量一个更具描述性的名称可以帮助理解代码。
  • 输入验证:尽管题目假设了输入的有效性,但在实际编程中,增加对输入数据的基本验证是一个好习惯。
  • 使用const引用避免拷贝:在dfs函数中,将adj[u]的迭代器定义为const引用可以避免不必要的副本创建。

改进代码(AI)

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

const int MAX_NODES = 6001; // 更具描述性的常量名
vector<int> adj[MAX_NODES];
int happy[MAX_NODES];
int dp[MAX_NODES][2]; 

// 深度优先搜索,计算以u为根的子树的最大幸福值路径和
void dfs(int u, int parent) {
    // 初始化当前节点的两种状态
    dp[u][1] = happy[u]; // 包含u的最大路径和
    dp[u][0] = 0;        // 不包含u的最大路径和
    
    // 遍历子节点
    for (const int &v : adj[u]) { // 使用const &避免拷贝
        if (v != parent) {
            dfs(v, u); // 递归搜索子节点
            // 更新u的dp值
            dp[u][1] += dp[v][0]; // 考虑子树不含根节点的最大和
            dp[u][0] += max(dp[v][0], dp[v][1]); // 子树最大和,选择包含或不包含子树根
        }
    }
}

int main() {
    int n;
    cin >> n;
    if (n < 1 || n > MAX_NODES) { // 输入验证
        cerr << "Invalid node count!" << endl;
        return -1;
    }
    
    // 读取每个节点的幸福值
    for (int i = 1; i <= n; ++i)
        cin >> happy[i];
    
    // 构建树的邻接表
    for (int i = 1; i < n; ++i) {
        int l, k;
        cin >> l >> k;
        adj[k].push_back(l);
        adj[l].push_back(k); // 注意:无向图需双向添加
    }
    
    // 从根节点开始深度优先搜索
    dfs(1, -1);
    
    // 输出结果
    cout << max(dp[1][0], dp[1][1]) << endl; // 输出最大幸福值路径和
    
    return 0;
}

其它代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 6010;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
bool has_fa[N];
void add(int a, int b)
{
    e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
void dfs(int u)
{
    f[u][1] = happy[u];
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        dfs(j);
        f[u][1] += f[j][0];
        f[u][0] += max(f[j][0], f[j][1]);
    }
}
int main()
{
    int n; cin >> n;
    for(int i = 1; i <= n; i++) cin >> happy[i];
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i++)
    {
        int a, b; cin >> a >> b;
        add(b, a);
        has_fa[a] = true;
    }
    int root = 1;
    while(has_fa[root]) root++;
    dfs(root);
    cout << max(f[root][0], f[root][1]) << endl;
    return 0;
}

代码思路

  1. 数据结构与初始化:

    • 使用邻接表表示树的结构,h[N]存储每个节点的首条边索引,e[N]ne[N]分别存储边的终点和下一条边的索引,idx作为边的计数器。
    • happy[N]数组存储每个节点的幸福值。
    • f[N][2]二维数组,其中f[u][0]表示以节点u为根的子树中不包含u的最大权值和路径,f[u][1]表示包含u的最大权值和路径。
    • has_fa[N]数组标记节点是否有父节点,用于后续寻找根节点。
  2. 建立邻接表:通过add(a, b)函数添加边,这里建立的是从孩子指向父节点的边,因为输入时给的是每个节点的父节点。

  3. 深度优先搜索(DFS):dfs(u)函数递归遍历以节点u为根的子树,计算f[u][0]f[u][1]。对于每个子节点j,先递归调用dfs(j),然后根据子节点的最优解更新当前节点的最优解。f[u][1] += f[j][0]表示当前节点选中的情况,f[u][0] += max(f[j][0], f[j][1])表示当前节点不选或选中的情况,取两者的较大值累加。

  4. 寻找根节点:因为输入的边是父节点指向子节点,可以通过一个标志数组has_fa来找到没有父节点的节点,即树的根。

  5. 输出结果:在遍历完树后,根节点的f[root][0]f[root][1]中较大的一个就是所求的最大权值和。

利用动态规划的思想来解决树上的问题,通过两次遍历(一次构建树,一次DFS计算最优解)实现了问题的求解。

标签:int,adj,dfs,DP,happy,285,节点,dp,AcWing
From: https://blog.csdn.net/u014114223/article/details/139815582

相关文章

  • 数位统计DP——AcWing 338. 计数问题
    数位统计DP定义数位DP(DigitalDP)是一种用于解决与数字的数位相关问题的动态规划算法。它将数字的每一位看作一个状态,通过转移状态来计算满足特定条件的数字个数或其他相关统计信息。运用情况统计满足特定条件的数字个数,例如在给定范围内有多少个数字满足某些数位特征。计算......
  • Manifest V3 getBackgroundPage() 返回 undefined 或报错 You do not have a backgrou
    省流:无解了,老老实实 sendMessage罢这件事挺奇怪的,因为我看官方文档就是这么写的,也没什么特别说明,版本也是最新的,就挺奇怪的……在翻了一大圈,之后看到了这篇帖子:意思就是说,api已经不能用了,文档因为人手不够就没更新…… 此外还有一个 chrome.runtime.getBackgroundPage......
  • CF1285F Classical?
    首先一个很自然的想法就是枚举钦定\(\gcd(a_i,a_j)\)的值为\(d\),然后再枚举所有\(d\)的倍数,钦定它们之间的\(\gcd=d\)时才统计贡献但这样本身浪费了不少时间在重复的枚举上,不妨换个思路,我们直接预先将每个数所有的约数都放入一个集合\(S\)显然\(|S|\le10^5\),而我们此时可以把条......
  • TCP与UDP详解:层次、区别及应用场景
    TCP和UDP的层次及区别详解所属层次TCP(TransmissionControlProtocol,传输控制协议)和UDP(UserDatagramProtocol,用户数据报协议)都属于OSI模型中的传输层(第四层)。在传输层,协议的主要作用是为端到端的通信提供逻辑通信,并确保数据在网络上传输的可靠性和顺序。TCP和UDP的区别......
  • api-ms-win-shcore-scaling-l1-1-1.dll解析及缺失解决策略:确保Windows高DPI显示正常
    api-ms-win-shcore-scaling-l1-1-1.dll是Windows操作系统中的一个API接口库文件,属于WindowsShellCommonDLLs(Shell核心动态链接库)的一部分。这个特定的DLL文件与Windows的高DPI(每英寸点数)缩放功能紧密相关,支持应用程序在不同分辨率和缩放设置下正确显示界面元素,确保UI的清晰......
  • hdu2845dp问题
    看了一眼题目,简单dp问题,但超时了一晚上,试了各种方法无法解决,最终放弃java,改用C直接过,我哭了。。。。#include<stdio.h>#include<string.h>#definemaxn200010intdp[maxn],ans[maxn],map[maxn];intmax(intx,inty){returnx>y?x:y;}intmain(){inti,j;......
  • MPC与DDP结合案例
    MPC与DDP结合概要MPC与DDP的关系1.相似性:优化过程:都涉及到优化一个代价函数以求得最优控制输入。动态模型:都依赖于系统的动力学模型来预测和更新系统状态。2.差异性:时间尺度:MPC是在线控制,每次只优化有限预测区间的控制输入,然后在每个时间步长重新优化。......
  • QOJ1285 Stirling Number
    QOJ传送门因为\(x^{\overlinep}\equivx^p-x\pmodp\),所以设\(n=pq+r\),其中\(r\in[0,p-1]\),则有:\[\begin{aligned}x^{\overlinen}&=(\prod\limits_{i=0}^{q-1}(x+ip)^{\overlinep})(x+pq)^{\overliner}\\&=(x^p......
  • dp题选做
    1.在两个数列之间有两个整数数列\(a_1,a_2,\cdots,a_n\)和\(b_1,b_2,\cdots,b_n\)。我们的任务是找出满足以下条件的数列\(c_1,c_2,\cdots,c_n\):对\(i=1,2,\cdots,n\),\(a_i\lec_i\leb_i\)对\(i=1,2,\cdots,n-1\),\(c_i\lec_{i+1}\)所有\(c_i\)都是整数满足这......
  • AcWing 3719. 畅通工程
    某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。所有道路都是双向的。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条双向道路?输入格式第......