(P2014[CTSC1997])选课(树上背包)

题目链接:[CTSC1997] 选课 - 洛谷

分析:这是一道树上背包的模板题,什么是树上背包呢?就是说物品之间有依赖关系,就比如说选a就必须要先选b,选b又必须要先选c,大概就是这个意思,类似于我们大学课程之间的关系,但又不完全是,因为我们大学课程中一门课可能有多门先修课,但是树上背包要求每一个物品只能依赖于其他1个物品或者不依赖于其他物品,这个也比较容易理解,毕竟树不就是一对多的关系吗?知道了这个之后,我们来对这道题目进行讲解,首先设f[i][j]表示在以i为根节点的子树中选了j门课所获得的最多学分,下面我们分析动态转移方程:

我们只能用一个点的子节点去更新这个点,所以假如我们当前这颗树的最优解选了k门课,其中一定包含根节点这门课(这个条件一定不能忽视),然后我们可以枚举每一个子节点选的课程数i,然后k-i就是以x为根的子树中去掉当前子节点之外其他节点所选的课程数了,就这样更新完就可以得到最后的答案了。

下面给出一个错误的更新代码,我来带着大家分析一下这样更新为什么是错误的:

for(int j=1;j<=m+1;j++)
for(int k=1;k<=j-1;k++)
	f[x][j]=max(f[x][j],f[x][k]+f[son][j-k]);

我们当前更新的是以第x个节点为根的子树,用到的子节点是son,j是以x为根的子树中所选的节点总数,而k是用到的除了当前用到的子节点之外的节点,而j-k就是以当前子节点为根的子树中所选的节点数目了,明白了这些之后我来分析一下这样为什么是错的。

我们假如当前j是7,那么k可以是1~6,在我们k是7之前已经更新完了k是1~6的情况,而且这个更新过程中可能用到了以当前子节点为根的子树中的节点,这就会导致我们先在更新较大的j时f[x][k]也会包含以当前子节点为根的子树中的节点,而由我们刚才的分析明确可以看出k是用到的除了当前用到的子节点之外的节点数,所以这就会导致错误

更正的方法也比较简单,只需要把j的那层循环倒置即可,也就是

for(int j=m+1;j>=1;j--)
for(int k=j-1;k>=1;k--)
	f[x][j]=max(f[x][j],f[x][k]+f[son][j-k]);

这样我们在用小于j的k更新j时f[x][k]还没有被更新到,这样更新就不会出现问题了,这里还需要说明的一个技巧就是可能有多个节点没有先修课,所以我们最终的答案就很难统计,我们可以建立一个权值为0的节点,然后让所有没有先修课的课程的先修课为0,这样我们最后就只需要通过f[0][m+1]就可以知道答案了,为什么是m+1呢?因为这个时候我们包含了第个节点,而这只是一个虚拟节点,所以就需要把选m门课改成选m+1门课,前提是别忘了将第0门课的学分设置为0.

下面是代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1003;
int f[N][N];//f[i][j]表示以i为节点的子树中选j门课所获得的最大学分 
int n,m,h[N],e[N],ne[N],w[N],idx; 
void add(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int fa)
{
	f[x][1]=w[x];//只选根节点 
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int son=e[i];
		if(son==fa) continue;
		dfs(son,x);
		for(int j=m+1;j>=1;j--)
		for(int k=j-1;k>=1;k--)
			f[x][j]=max(f[x][j],f[x][k]+f[son][j-k]);
	}
} 
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	int k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&k,&w[i]);
		if(k==0) add(0,i);//第i门课没有先修课,所以我们可以给其加上一门权值为0的先修课0 
		else add(k,i);
	}
	dfs(0,-1);
	printf("%d",f[0][m+1]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值