【LuoGu】2014 选课——树上DP

[CTSC1997] 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 \(N\) 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 \(M\) 门课程学习,问他能获得的最大学分是多少?

输入格式

第一行有两个整数 \(N\) , \(M\) 用空格隔开。( \(1 \leq N \leq 300\) , \(1 \leq M \leq 300\) )

接下来的 \(N\) 行,第 \(I+1\) 行包含两个整数 $k_i $和 \(s_i\), \(k_i\) 表示第I门课的直接先修课,\(s_i\) 表示第I门课的学分。若 \(k_i=0\) 表示没有直接先修课(\(1 \leq {k_i} \leq N\) , \(1 \leq {s_i} \leq 20\))。

输出格式

只有一行,选 \(M\) 门课程的最大得分。

样例 #1

样例输入 #1

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

样例输出 #1

13

解决方案

树形DP

根据题意我们可以发现题目中给的图是一个森林,直接进行\(DP\)不太好处理。因此要使用一个技巧:超级结点
超级结点就是新建一个结点将所有连通块连接在一起使森林连接成一棵树。然后从超级结点出发就可以遍历整棵树了。
建立超级结点后就可以来考虑动态规划了。

\(f[i][j]\)表示在以\(i\)为根结点的子树上选取\(j\)个结点所能获得的最大分数。考虑结点\(k \in i的子结点\),如果我们不从以\(k\)为根结点的子树上选结点的话,那么\(f[i][j]=f[i][j]\),如果我们从以\(k\)为根结点的子树上选择\(cnt\)个子结点的话,\(f[i][j]\)就由两部分组成:从子树\(k\)中选出的\(cnt\)个结点,以及从\(i\)的其他子树中选出来的\(j-k\)个结点,状态转移方程为\(f[i][j] = f[i][j - cnt] + f[k][cnt]\)。因此总的状态转移方程为$$f[i][j]=max_{k \in son[i]}(f[i][j], f[i][j-cnt] + f[k][cnt]), cnt \in [0, m]$$

#include<bits/stdc++.h>

const int N = 330;

int n, m;
std::vector<std::vector<int>> g(N);
int w[N];
int f[N][N];

void dfs(int u){
	for(auto v:g[u]){
		dfs(v);
		for(int j = m + 1; j >= 0; j -- ){
			for(int k = 0; k < j; k ++ ){
				f[u][j] = std::max(f[u][j], f[u][j - k] + f[v][k]);
			}
		}
	}
}

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);

	std::cin >> n >> m;

	for(int i = 1; i <= n; i ++ ){
		int k, s;
		std::cin >> k >> s;
		g[k].push_back(i);
		w[i] = s;
		f[i][1] = s;
	}

	dfs(0);

	std::cout << f[0][m + 1];

	return 0;
}

题解参考:https://www.cnblogs.com/fusiwei/p/13753292.html

posted @ 2023-09-09 20:24  天涯海角寻天涯  阅读(22)  评论(0)    收藏  举报