P2014 [CTSC1997] 选课 树形dp
解题思路
问题分析
这道题是一个典型的树形依赖背包问题,需要在一棵课程依赖树中选择若干门课程,满足:
-
选择某门课程前必须先选择它的先修课程
-
总共选择 M 门课程
-
获得的学分总和最大
动态规划状态设计
定义 dp[u][j]:表示在以 u 为根的子树中选择 j 门课程能获得的最大学分
状态转移方程
对于每个节点 u 和它的子节点 v:
-
如果不选 u,则不能选任何子节点
-
如果选 u,则可以分配 k 门课程给子节点 v 的子树
-
转移方程:
dp[u][j] = max(dp[u][j], dp[u][j-1-k] + dp[v][k] + w[v])解释:
-
j-1-k:给其他子树分配的课程数(总课程数 j 减去当前课程 1 再减去给子树的 k) -
dp[v][k]:子树 v 选择 k 门课程的最优解 -
w[v]:课程 v 的学分
-
关键点说明
-
虚拟根节点:代码中使用 0 作为虚拟根节点连接所有无先修课的课程
-
倒序枚举 j:类似背包问题的空间优化,避免重复计算
-
课程数限制:M 门课程包含所有选择的课程(包括先修课程)
代码注释
#include<bits/stdc++.h> using namespace std; const int N = 1e3 + 10; // 数组大小 vector<int> g[N]; // 邻接表存储课程树,g[u]表示u的所有子课程 int n, m; // n-课程总数,m-需要选择的课程数 int w[N]; // w[i]表示第i门课程的学分 int dp[N][N]; // dp[u][j]: 以u为根的子树选择j门课程的最大学分 void dfs(int u) { // 遍历u的所有子课程 for(int i = 0; i < g[u].size(); i++) { int v = g[u][i]; // 子课程v dfs(v); // 递归处理子课程 // 动态规划转移(类似背包问题倒序枚举) for(int j = m; j >= 1; j--) { // 枚举当前可选的课程总数 for(int k = 0; k <= j - 1; k++) { // 分配给子课程v的课程数(最多j-1门) // 状态转移:选择u课程(隐含),并分配k门课程给v的子树 // 因为要选v课程,必须先选u课程(u是v的先修课) dp[u][j] = max(dp[u][j], dp[u][j - 1 - k] + dp[v][k] + w[v]); } } } } int main() { cin >> n >> m; // 输入课程总数和需要选择的课程数 // 建树(0作为虚拟根节点) for(int i = 1; i <= n; i++) { int x, y; cin >> x >> y; // x是先修课,y是当前课程的学分 w[i] = y; // 存储学分 g[x].push_back(i); // 添加到先修课的子节点中 } dfs(0); // 从虚拟根节点0开始DFS cout << dp[0][m]; // 输出选择m门课程的最大学分 return 0; }

浙公网安备 33010602011771号