洛谷P2014 选课 背包类树形dp 个人理解

题目:https://www.luogu.org/problemnew/show/P2014

首先,根据题意我们能很清楚的想象出一棵树。(为没有先修课的所有课加上一个虚拟的父亲节点)

一共可以选m门课,我们需要在树中选m个节点,使得所有节点上的权值(学分)加起来最大,注意每个节点可以背选当且仅当其父节点背选。

这是背包类树形dp的入门题。

其实很容易意识到这应该跟动态规划有关,因为节点有限制(必须选父节点),所以最优解不一定会包括权值最大的那单个节点。

对于树本题,我们设 dp[i][j] 。

i: 节点序号

j:在以i节点为根节点的树,选j门课     

dp[i][j]:在以i节点为根节点的树,选j门课能够获得的最多学分

本题与一维背包问题有共同之处

看图:

洛谷P2014 选课 背包类树形dp 个人理解_第1张图片

dp[fat][m]是其各自子树最优解之和,再加上自身的权值

因为有一个m的限制,所有子树选了的课加起来不能超过m,因此我们可以将同一个子树看成不同的物品

例如son1 就可以看成 选了1节课的,选了2节课的,选了3节课的,同理将son2,son3都看成不同的物品,这是不是有点类似于一维背包了。背包大小就是m,选了k节课,那么k就是货物的重量,k加在一起不能超过m,求最大总权值。

但是还有一点就是一棵子树拆开来的不同物品不能同时选,比如son1拆开的那3个就只能选1个。

代码:

#include
using namespace std;
const int maxn = 307;
vector cls[maxn];
int val[maxn],dp[maxn][maxn];
int n, m;
void solve(int num) {	
	dp[num][0] = 0;
	for (int i = 0; i < cls[num].size(); i++) {
		int &son = cls[num][i];
		solve(son);
		for (int j = m; j >= 0; j--) { //一维背包    j:fat     k: son
			for (int k = j; k >= 0; k--) {
				dp[num][j] = max(dp[num][j], dp[num][j - k] + dp[son][k]);
			}
		}
	}
	if (num != 0) for (int i = m; i > 0; i--) dp[num][i] = dp[num][i - 1] + val[num]; //加上自己
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int fat, sco;
		cin >> fat >> sco;
		cls[fat].push_back(i);
		val[i] = sco;
	}
	solve(0);
	cout << dp[0][m] << endl;
	return 0;
}

 

 

你可能感兴趣的:(acm)