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;
}
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;
}
上一篇: 编写测试用例的方法
推荐阅读