题目传送门:
P2014 [CTSC1997] 选课 - 洛谷 (luogu.com.cn)
前言:
这道题是一个典型的树形动态规划问题,解题的核心思路是将课程之间的先修关系构建成一棵树,然后通过深度优先搜索(DFS)遍历树的节点,同时运用动态规划的思想来计算选择指定数量课程所能获得的最大学分,难度不是很大,题目明来源也是来自 CTSC国家选拔赛中的其中一道,小亦来带大家详细讲解。
#题目思路
1、构建课程树:
题目中给出了每门课程的先修课信息,我们可以把这种先修关系看作是树的父子关系,即先修课是父节点,后续课程是子节点。如果一门课程没有先修课,我们可以创建一个虚拟的根节点(这里设为 0
),将这些没有先修课的课程作为虚拟根节点的子节点。
具体的实现时,我们可以使用一个邻接表来存储这棵树。例如,用 tree[i]
表示节点 i
的所有子节点列表。
2、状态定义:
我们使用二维数组 dp[u][j] 来表示状态,其中 u 表示当前树的根节点, j 表示在以节点 u 为根的子树中选择的课程数量, dp[u][j] 的值表示在这种的情况下所能获得的最大学分。
3、状态转移:
对于每个节点 u ,我们需要考虑如何从其子节点的状态转移得到 dp[u][j] 的值。假设节点 u 有子节点 v ,我们可以通过枚举子节点 v 选择的课程数量 k 来更新 dp[u][j] 。
状态转移方程为:
这里的 credits[v] 表示的是节点 v 对应的课程学分。 dp[u][j-k-1] 表示在以 u 为根的子树中除了子节点 v 之外选择 j-k-1 门的课程所能获得的最大学分,dp[u][j-k-1] 表示的在以 v 为根的字节数中选择了 k 门课程所能获得的最大学分。我们需要所有可能 k 的值,取其中最大值来更新 dp[u][j]。
4、DFS 深度优先搜索:
为了实现状态转移,我们需要对树进行深度优先搜索。从根节点开始,递归地处理每个节点及其子节点。在处理每个节点时,先递归调用 DFS 函数处理其子节点,然后根据子节点的状态更新当前节点的 dp
值。
5、最终结果:
最后,我们要求的是选择 M
门课程所能获得的最大学分,也就是 dp[0][M]
的值,其中 0
是虚拟根节点
##复杂度分析:
1、时间复杂度:
由于有三重嵌套循环,时间的复杂度为 ,其中 N 是可能的数量, M 是选择的课程数量。
2、空间复杂度:
主要用于存储 DP 数组,空间复杂度为 。
###代码:
#include <bits/stdc++.h>
using namespace std;
const int M = 305;
int n, m;
int p[M];
int credits[M];
vector<int> tree[M];
int dp[M][M];
void DFS(int u) {
for (int i = 0; i < tree[u].size(); ++i) {
int v = tree[u][i];
DFS(v);
for (int j = m + 1; j > 0; --j) {
for (int k = 0; k < j; ++k) {
dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + credits[v]);
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> p[i] >> credits[i];
tree[p[i]].push_back(i);
}
DFS(0);
cout << dp[0][m] << endl;
return 0;
}