树形DP 洛谷P2014 选课

洛谷P2014 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?

输入输出格式

输入格式:

第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=300)

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

输出格式:

只有一行,选M门课程的最大得分。

好困啊~~~

这道题做的不好。

一开始的做法:
​ 设(f(i)(j))表示在以i为根的子树中,选择j个点并且一定选择i的最大价值。

​ 状态倒是设对了。但是转移就不会了。

​ 想的是树上的分组背包。

​ 将当前点的每个儿子看成一类物品,然后就可以枚举这一类物品中选多少个。

​ 应该是没问题,但是太困写不动了。。。

​ 而且我犯了一个错误就是对于当前的点u,应该枚举一层这个点的子树中选择多少个,所以我们应该记录当前的这个数的大小,并用其作为上界,但我是写了一个记忆化搜索每一次将传入的参数t当做上界,就不对了。

好了,感觉我的做法思路很乱套。

开始说一说正确做法吧。

​ 状态很好设:设(f(i)(j))表示在以i为根的子树中,选择j个点并且一定选择i的最大价值。

​ 重点是怎么去推。

​ 这里用到了一个东西叫做多叉转二叉。也就是说我们要记录当前节点的子树大小作为枚举上界,所以我们可以每一次将当前点现在所枚举到的子树的大小加上,就是开一个变量记录当前的子树大小,在转移之前先将当前枚举到的子节点的size加上。

​ 这里有一个DP的思想,个人感觉类似记忆化搜索吧,挺重要的,就是我们当前要用v去转移u的状态,那么肯定的,我们应该知道v的状态(当然也可以在转移时记忆化搜索出v的状态),所以我们在转移u之前就应该先将v转移好。

#include<iostream>
#include<cstdio>
using namespace std;
const int wx=3017;
inline int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0';ch=getchar();}
	return sum*f;
}
struct e{
	int nxt,to;
}edge[wx*2];
int n,m,num,x;
int head[wx],a[wx],f[317][317],size[wx];
void add(int from,int to){
	edge[++num].nxt=head[from];
	edge[num].to=to;
	head[from]=num;
}
int dfs(int u){
	f[u][1]=a[u];int tot=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;tot+=dfs(v);
		for(int j=tot;j>=1;j--){
			for(int k=1;k<j;k++){
				f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
			}
		}
	}
	return tot;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		x=read();a[i]=read();
		add(x,i);
	}
	dfs(0);
	printf("%d
",f[0][m+1]);
	return 0;
}
//f[i][j]表示在以i为根的子树中选择j个点并且一定选i这个点的最大价值 
//f[i][j]=max(f[i][j],f[v][k]+a[v]+f[i][j-k])
原文地址:https://www.cnblogs.com/wangxiaodai/p/9762448.html