CodeVS1378选课

题目

  http://codevs.cn/problem/1378/

题解

  树形DP入门题目。

  两种方法:

  两种方法的共性首先是用一个大根节点把森林中所有子树的根节点连向它,然后就成了在整棵树上选择M+1个节点的最大获利。

  法一:树上背包

  这里可能要用一些泛化物品的思想。

  令f[i][j]表示i这棵子树选择了j个课程的最大获利,一种情况肯定不能暴力枚举每个儿子分别选几个,这样是阶乘级别的,可以这样,每个儿子看成一个泛化物品,给他一定的容量可以得到一定的价值,f[son][k]就是son这个儿子在得到容量k的情况下的最大价值,那么你可以把所有儿子分别和父亲进行merge,更新父亲的f数组。这里就不多费口舌了要讲的话还得画图才能差不多理解。自行度娘。

   法二:多叉转二叉

  左儿子右兄弟。

  转成二叉树之后考虑怎样转移符合题意。题意:不能只选儿子而不选父亲,可以选父亲不选儿子。那就是用f[i][j]表示以i为根的数选择j个点,那么转移有两种情况,第一种强制选择根节点,枚举左右儿子分别选多少,然后加起来取max,左儿子选择了k个,就代表原来树上i的所有儿子一共选了k门。右儿子选k个,就代表点i在原树上的兄弟们一共占选择了k门课程,这样f[i][j]在这棵树上代表的是当前树选择了j门,在原树上代表一个点的从后i往后连续的几个儿子以供选择了j门课程的最大获利。第二种就是当前点不选,那么明显左子树都不能选,所以就直接由f[right[i]][j]转移。

  综上,f[i][j]=max{f[right[i][j]],f[left[i]][k]+w[i]+f[right[i]][j-1-k]}

  其实两种算法的实质是相同的,把第二种方法画个图模拟一下,发现其实就是法一中物品合并的过程,只不过是儿子先和儿子合并,然后父亲在和所有儿子的总和合并。

代码

//选课 树形DP(树上背包)
#include<cstdio>
#include<algorithm>
#define maxn 500
using namespace std;
int v[maxn],n,f[maxn][maxn],m,next[maxn],son1[maxn];
void dfs(const int r)
{
	int i,j,k,p;
	if(son1[r]==0)
		for(i=1;i<=m+1;i++)f[r][i]=v[r];
	p=son1[r];
	while(p)
	{
		dfs(p);
		for(i=m+1;i>=1;i--)
			for(k=1;k<=i;k++)
				f[r][i]=max(f[r][i],f[r][k]+f[p][i-k]);
		p=next[p];
	}
}
int main()
{
	int i,j,x,y,fa;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		scanf("%d%d",&fa,&v[i]);
		if(son1[fa])next[i]=son1[fa];
		son1[fa]=i;
		f[i][1]=v[i];
	}
	dfs(0);
	printf("%d\n",f[0][m+1]);
	return 0;
}
//树形DP(多叉转二叉)
#include <cstdio>
#include <algorithm>
#define maxn 500
using namespace std;
int left[maxn], right[maxn], fa[maxn], N, M, f[maxn][maxn], w[maxn];
void input()
{
	int x, i;
	scanf("%d%d",&N,&M);
	for(i=1;i<=N;i++)
	{
		scanf("%d%d",fa+i,w+i);
		if(fa[i]==0)fa[i]=N+1;
		x=left[fa[i]];
		left[fa[i]]=i;
		right[i]=x,fa[x]=i;
	}
}
void dp(int pos)
{
	int j, k;
	if(left[pos])dp(left[pos]);
	if(right[pos])dp(right[pos]);
	for(j=1;j<=M+1;j++)
	{
		for(k=0;k<j;k++)
			f[pos][j]=max(f[pos][j],f[left[pos]][k]+w[pos]+f[right[pos]][j-k-1]),
		f[pos][j]=max(f[pos][j],f[right[pos]][j]);
	}
}
int main()
{
	input();
	dp(N+1);
	printf("%d\n",f[N+1][M+1]);
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值