DP起手练习10(树上的背包问题)

题目描述

例题:[CTSC1997]选课

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

输入格式

第一行有两个整数 N,M用空格隔开。 ( 1 ≤ N ≤ 300 , 1 ≤ M ≤ 300 ) {( 1 \leq N \leq 300 , 1 \leq M \leq 300)} (1N300,1M300).
接下来的N行,第i+1行包含两个整数 k i {k_i} ki s i {s_i} si
表示第i门课的直接先修课, s i {s_i} si 表示第I门课的学分.
k i {k_i} ki=0表示没有直接先修课.
( 1 ≤ k i ≤ N , 1 ≤ s i ≤ 20 ) {(1 \leq {k_i} \leq N,1 \leq {s_i} \leq 20)} 1kiN,1si20.

输出格式

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

输入输出样例
输入 #1
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
输出 #1
13

解题思路

这道题是一个经典的树上背包问题,我们常常会用 f [ i ] [ j ] {f[i][j]} f[i][j]来表示以i为根节点的子树选j个点所得的的最大权值和.本题为了方便起见,我们可以把0作为这棵树(考虑实际选课不能成环!)权值为0的根节点(加上0节点后我们总共需要选m+1个节点),从根节点开始将树遍历一遍并用 递 归 + D P {递归+DP} +DP求出每个节点上的 f [ i ] [ j ] {f[i][j]} f[i][j]即可.其中从u到v节点我们有DP状态转移方程:(典型的背包)
f [ u ] [ j ] = m a x ( f [ u ] [ j ] , f [ v ] [ k ] + f [ u ] [ j − k ] ) , j ∈ [ 1 , m + 1 ] , k ∈ [ 0 , j ] 。 {f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]),j∈[1,m+1],k∈[0,j]。} f[u][j]=max(f[u][j],f[v][k]+f[u][jk]),j[1,m+1],k[0,j]
最后 a n s = f [ 0 ] [ m + 1 ] . {ans=f[0][m+1].} ans=f[0][m+1].

代码

#include
using namespace std;

int n,m,tot,ans=-1;
int fi[10005],nxt[10005],to[10005];
int val[1005];
int f[1005][1005];

inline int lian(int u,int v)//邻接表
{
	nxt[++tot]=fi[u];
	fi[u]=tot;
	to[tot]=v;
}

inline int Dfs(int u)//将整棵树遍历
{
	for(int i=fi[u];i;i=nxt[i])
	{
		int v=to[i];
		Dfs(v);
		for(int j=m+1;j>=1;j--)//倒着枚举,否则会错.
			for(int k=0;k<j;k++)
				f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]);//DP核心方程
	}
}

int main()
{

	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int u;
		scanf("%d%d",&u,&f[i][1]);//初始化f[][]数组.
		lian(u,i);
	}
	Dfs(0);
	printf("%d\n",f[0][m+1]);
	return 0;
}

qwq.

你可能感兴趣的:(树形动态规划,————DP————)