树上背包练习

P2014 [CTSC1997]选课
P2014 [CTSC1997]选课

Solution

树上背包模板题

因为有多节课是没有先修课的,所以并不是只有一棵树,用一个0号点作为没有先修课的课程的先修课,这样就合并成了一棵树,只要选取m+1个点(必选0)即可。

转移方程: dp[u][j] = max(dp[u][j - k] + dp[v][k],dp[u][j]);
01背包,所以j要倒序枚举

代码

#include <bits/stdc++.h>
using namespace std;
const int SZ = 320;
typedef long long ll;
int n,m,fist[SZ],temp;
int dp[SZ][SZ],b[SZ],a;//dp[i][j] 为在i为根的子树上选择j个节点的最大学分 
struct zt
{
	int v,nxt;
}line[SZ];
inline void add(int a,int b)
{
	line[++ temp] = (zt){b,fist[a]};
	fist[a] = temp;
}

inline int dfs(int u)
{
	dp[u][1] = b[u];
	for(int i = fist[u];i != -1;i = line[i].nxt)
	{
		int v = line[i].v;
		dfs(v);
		for(int j = m;j >= 2;j --)
			for(int k = 1;k < j;k ++)
				dp[u][j] = max(dp[u][j - k] + dp[v][k],dp[u][j]);
	}
} 

int main()
{
	memset(fist,-1,sizeof(fist));
	scanf("%d%d",&n,&m);
	m ++;
	for(int i = 1;i <= n;i ++)  
	{
		scanf("%d%d",&a,&b[i]);
		add(a,i);
	}
	dfs(0);
	printf("%d\n",dp[0][m]);
	return 0; 
} 

 

P1273 有线电视网
P1273 有线电视网

Solution

与上题类似,改为只选叶子节点,同时减去边的花费。

令dp[i][j]为以i为根的子树中选择j个叶子节点的最大收益。
转移方程:dp[u][j] = max(dp[u][j],dp[u][j - k] + dp[v][k] - line[i].w)

最终结果可能是负值,所以dp数组初始化为一个极小值。

代码

#include <bits/stdc++.h>
using namespace std;
const int SZ = 3000 + 20;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int n,m,fist[SZ],val[SZ],temp,dp[SZ][SZ];
struct zt
{
	int v,nxt,w;
}line[SZ];

inline void add(int x,int y,int z)
{
	line[++temp] = (zt){y,fist[x],z};
	fist[x] = temp;
}

inline int dfs(int u)
{
	if(u > n - m) // 叶子 
	{ 
		dp[u][1] = val[u];
		return 1;
	}
	int sum = 0; // 当前节点为根时,子树中叶子节点的数量 
	for(int i = fist[u];i != -1;i = line[i].nxt)
	{
		int v = line[i].v;
		int t = dfs(v);
		sum += t;
		for(int j = sum;j >= 1;j --)
			for(int k = 1;k <= t;k ++)
				if(j - k >= 0) dp[u][j] = max(dp[u][j - k] + dp[v][k] - line[i].w,dp[u][j]);
	}
	return sum;
}

int main()
{
	memset(fist,-1,sizeof(fist));
	memset(dp,~0x3f,sizeof(dp));
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n - m;i ++)
	{
		int k,a,b;
		scanf("%d",&k);
		for(int j = 1;j <= k;j ++)
		{
			scanf("%d%d",&a,&b);
			add(i,a,b);
		}
	}
	for(int i = n - m + 1;i <= n;i ++) scanf("%d",&val[i]);
	for(int i = 1;i <= n;i ++) dp[i][0] = 0; 
	dfs(1);
	for(int i = m;i >= 0;i --)
	{
		if(dp[1][i] >= 0) 
		{
			printf("%d\n",i);	
			break;
		}
	}
	return 0;
}

2020.7.12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值