欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-22 11:58:40
...

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

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

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

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

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

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

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

0713-浅谈树形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;
}		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;
}

 

相关标签: 树形dp

上一篇: 编写测试用例的方法

下一篇: