P2014 [CTSC1997] 选课【题解】

P2014 [CTSC1997] 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 NNN 门功课,每门课有若干学分,分别记作 s1,s2,⋯ ,sNs_1,s_2,\cdots,s_Ns1,s2,,sN,每门课有一门或没有直接先修课(若课程 aaa 是课程 bbb 的先修课即只有学完了课程 aaa,才能学习课程 bbb)。一个学生要从这些课程里选择 MMM 门课程学习,问他能获得的最大学分是多少?

输入格式

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

接下来的 NNN 行,第 i+1i+1i+1 行包含两个整数 kik_ikisis_isikik_iki 表示第 iii 门课的直接先修课,sis_isi 表示第 iii 门课的学分。若 ki=0k_i=0ki=0 表示没有直接先修课 (0≤ki≤N(0 \leq {k_i} \leq N(0kiN,1≤si≤20)1 \leq {s_i} \leq 20)1si20)

输出格式

只有一行,选 MMM 门课程的最大学分。

输入输出样例 #1

输入 #1

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

输出 #1

13

解析

废话不多说,切入正题!

如果将 000 点当做根节点的话,这道题的数据结构显然是树形的,且可以将题意简化成:给定一个以 000 为根节点的树,除根以外有 nnn 个点,每个点都有点权,现保留除 000 以外的点 mmm 个,且保留的点构成以 000 为根节点的树,问保留的点的最大权值为多少?

思考如何动态规划。我们可以发现对于某个节点 uuu ,它可能有多个儿子,设它的子节点集合为 VuV_uVu。而我们发现,在处理以 uuu 为根节点的树时,会涉及到 以 uuu 的儿子为根的子树 的答案,但我们并不关心具体怎么选。所以想到如下策略:

①定义:dpu,jdp_{u,j}dpu,j 表示以 uuu 为根节点的树在保留 jjj 个点时所能获得的最大权值。

②初始状态:
因为以 uuu 为根节点的树保留 000 个点时,啥权值也没有,所以dpu,0=0dp_{u,0}=0dpu,0=0;因为当保留一个点时只能保留根节点,子树权值和就是根权值,所以dpu,1=sidp_{u,1}=s_idpu,1=si

③状态转移方程:
这个比较难推。想象一下,面对一个点 uuu,它的儿子 v∈Vuv\in V_uvVu,我们对这个儿子可选可不选,这似乎是个 010101背包 了。于是把每个子树看做一个物品,子树选择的节点数看做物品重量 weiweiwei,子树权值和看做价值 valvalval,可选的点数看做背包空间。枚举物品 v∈Vuv\in V_uvVu ,背包空间 j∈[1,m]j\in[1,m]j[1,m] ( j=0j=0j=0 的情况不必计算,已初始化为 000,枚举时别忘了要倒序),和 010101背包 一样取舍各个子树。

然而由于无法确定各个子树选几个点时对原树来说更优,即每个物品的 weiweiweivalvalval 随子树选几个点而变化,所以我们还要枚举各棵子树保留 k∈[0,j)k\in[0,j)k[0,j) 个点 (显然单棵子树保留不能超过 jjj 个,还要留一个点的份额给 vvv 节点) 时的 weiweiweivalvalval。计算 010101背包 中的最大价值。(其实就是分组背包,即一类物品中只能选一个,显然以一个点为根的所有树不论大小都是一类)

终于得到动态规划转移方程(在代码里看或许肯定会更直观):
dpu,j=max⁡v∈Vumax⁡m≥j≥1max⁡0≤k<j(dpu,j−k+dpv,k)dp_{u,j}=\max_{v\in V_u}\max_{m\geq j\geq 1}\max_{0\leq k<j}{(dp_{u,j-k}+dp_{v,k})}dpu,j=vVumaxmj1max0k<jmax(dpu,jk+dpv,k)
看上去怪怪的,但想要用一个式子表达只能这样,奇特处解说如下:
当第二个 max⁡\maxmax 中的 jjj 变化时,dpu,jdp_{u,j}dpu,j 中的 jjj 也跟着变化,但 vvv 依旧不变,直到这个 vvv 的所有 jjj 枚举完。

代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
int s[305];
struct EDGE{
	int to,nxt;
}edge[305];
int head[305];
int tot;
void add(int u,int v){//链式前向星存图
	tot++;
	edge[tot].nxt=head[u];
	edge[tot].to=v;
	head[u]=tot;
}
int dp[305][305];//dp[u][j]表示u号节点选j门课时最大学分 
void dfs(int u){
	for(int i=head[u];i;i=edge[i].nxt){
		dfs(edge[i].to);//必须先处理儿子,因为想要算父亲,要用到儿子的各个状态
	}
	for(int i=head[u];i;i=edge[i].nxt){//枚举各棵子树
		int v=edge[i].to;
		for(int j=m;j>=1;j--){//倒着枚举背包空间
			for(int k=0;k<j;k++){//枚举这个子树保留k个点
				dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
			}
		}
	}
}
int main(){
	cin>>n>>m;
	m+=1;//0点必选,但它不应算在m中,所以可选点加一
	for(int i=1;i<=n;i++){
		int f;
		cin>>f>>dp[i][1];//直接把点权给dp[i][1]
		add(f,i);//存图
	}
	dfs(0);
	cout<<dp[0][m];
	return 0;
}

值得一提的是,大多数 树形DP树形DP树形DP 都是依赖 DFSDFSDFS 进行的,因为 DFSDFSDFS 能够完美完成子节点回溯到父节点的行为,这是 树形DP树形DP树形DP的关键。

谢谢阅读,文末求赞QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值