0713-浅谈树形dp-典型例题二

好啦好啦,终于发起了,绝对是机房的电脑跟我有仇,哼(¬︿̫̿¬☆)

类型二:普通树上的dp(上一篇讲的是二叉树,比较容易,如有遗忘戳一戳

普通树和二叉树的区别是啥子嘞,

就是说二叉树里一个结点下最多只有两个儿子结点,所以状态比较好转移(从儿子向上走)。但普通树就不止两个了,可能有多个,那就不好玩了╭(╯^╰)╮。可是,我们可以把普通树转成二叉树啊,一样的dp,熟悉的感觉~

转化的口诀(原则):左儿子,右兄弟

就是说当前结点的第一个儿子,放在它的左子树,其余的儿子挂在第一个儿子的右子树上。

那么对于每个结点而言,它左边的才是它真正的儿子,右边都是它兄弟(同一个父亲)


(大家凑合看看,然后自己试着转转)

如何检验是否转对了?

1.每个结点的信息没有改变(比如其父亲结点,儿子结点,还有的就因题而异了)

2.可以从二叉树再转回去(基本上这条如果做到了,就没什么大问题了)

那么转化的思想get到了,怎么具体用代码实现呢?

先不急,我们等会儿结合着典型例题来看吧



普通树已经转成二叉树了,接下来就好办多了

!上例题!

洛谷上不晓得有没有,应该有,反正我把这道题放出来,以后类似的题就不虚啦

选课(有点像有依赖的背包)

题目描述

学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了 N(N<300)门的选修课程,每个学生可选课程的数量 M 是给定的。学生选修了这M门课并考核通过就能获得相应的学分。
在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为1,2,3,…。 例如:

课号 先修课号 学分
1 1
2 1 1
3 2 3
4 3
5 2 4

表中 1 是 2 的先修课,2 是 3、4 的先修课。如果要选 3,那么 1 和 2 都一定已被选修过。

你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。

输入格式

输入文件的第一行包括两个整数 N、M(中间用一个空格隔开)其中 1≤N≤300,1≤M≤N。
以下N行每行代表一门课。课号依次为 1,2,…,N。每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。学分是不超过 10 的正整数。

输出格式

输出文件只有一个数,实际所选课程的学分总数。

样例数据 1

输入  



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

输出

13

每门课的直接先修课最多只有一门。”这句话告诉我们每门科只会对应一个父亲

两门课也可能存在相同的先修课。"这是在说明这是一棵普通树,每个节点下不一定只有两个儿子

这种无先修课的就是根节点啦,而无先修课的课不止一门,所以这是一个森林~(但和普通树转二叉树没什么区别,直接选中一棵树的根节点作为森林的根节点,然后左儿子,右兄弟)

然后对于每个节点而言,如果选他的左儿子,那么该节点必选,但如果选他的右儿子,就不一定要选根了(因为他们是兄弟关系呗)根据这个来状态转移,就和例题一类似了

下面是小蒟蒻自己想了然后敲的,转麻烦了(;′⌒`),大家尽可以忽略不看,但要提醒一点,也是本宝宝gg的地方。不要用0号节点来作为虚拟的根节点,会和空节点混淆的,用n+1都可以

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int n,m;
struct node{
	int fa,son[305],num,v;
	int lc,rc;
}a[309];
void build(int k){
	int i,j,now;
	int que[3009],qn;
	que[qn=1]=k;
	for(j=1;j<=qn;++j){
		int kk=que[j];
		now=a[kk].lc=a[kk].son[1];
		if(now) que[++qn]=now;
		for(i=2;i<=a[kk].num;++i){
			a[now].rc=a[kk].son[i];
			now=a[now].rc;
			que[++qn]=now;
		}
	}
}
int f[309][309];
int dp(int i,int j){
	if(j==0||i==0) return 0;
	if(a[i].lc==0&&a[i].rc==0) return a[i].v;
	if(f[i][j]) return f[i][j];
	if(a[i].rc) f[i][j]=dp(a[i].rc,j);
	
	for(int k=0;k<j;++k)
			f[i][j]=max(f[i][j],dp(a[i].lc,k)+dp(a[i].rc,j-k-1)+a[i].v);
	
	return f[i][j];
}
int main(){
	scanf("%d%d",&n,&m);
	int i,j,k;
	memset(a,0,sizeof(a));
	for(i=1;i<=n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		a[i].fa=x;
		a[i].v=y;
		a[x].num++;
		a[x].son[a[x].num]=i;
	}
	a[0].v=0;
	build(0);
	
	printf("%d",dp(a[0].lc,m));
	return 0;
}

下面放上ldw老师的,没有对比就没有伤害(让我去墙角哭一会儿)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,son[306],f[309][309];
struct node{
	int lc,rc,v;
}a[309];
int dp(int i,int j){
	if(i==0||j==0) return 0;
	if(a[i].lc==0&&a[i].rc==0) return a[i].v;
	if(f[i][j]) return f[i][j];
	if(a[i].rc) f[i][j]=dp(a[i].rc,j);
	for(int k=0;k<j;++k)
		f[i][j]=max(f[i][j],dp(a[i].lc,k)+dp(a[i].rc,j-k-1)+a[i].v);
	return f[i][j];
}
int main(){
	scanf("%d%d",&n,&m);
	int i,j,k;
	for(i=1;i<=n;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[i].v=y;
		if(!son[x]){//如果x还没有儿子 
			a[x].lc=i;
		}
		else  a[son[x]].rc=i;//左儿子,右兄弟 
		son[x]=i;
	}
	
	printf("%d",dp(a[0].lc,m)); 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81031589