[树上背包浅谈]

树上背包

在这个树中选取一定数量的点或边(也可能是其他属性),使得某种与点权或者边权相关的花费最大或者最小。解决这类问题,一般要考虑使用树上背包。

dp[i][j]表示在以i为根的树中选择j个结点所获得的最大值/最小值

有线电视网

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 3005;
int n,m,cost[maxn];
int head[maxn],cnt;
int dp[maxn][maxn];
struct Edge{
	int to,len,next;
}edge[maxn];

void add(int u,int v,int w)
{
	cnt ++;
	edge[cnt].to = v;
	edge[cnt].len = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
	return ;
}

int dfs(int u)//dfs返回的是以u为根节点的树,所包含的叶子结点数
{
	if(u > n-m) return 1;//说明到了叶子结点
	int num = 0;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v = edge[i].to;
		int nv = dfs(v);
		num += nv;
		for(int j=num;j>=0;j--)//类似滚动数组的思想,j要倒着写,这样j是不断变小的,如果j先更新小的,那么大的会被更新过的dp[u][j-k]更新,这是错误的
			for(int k=0;k<=min(nv,j);k++)
				dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].len);
	}
	return num;
} 

int main()
{
	memset(dp,128,sizeof(dp));//求最大值故初始化为最小
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-m;i++)
	{
		int num;
		scanf("%d",&num);
		for(int j=1;j<=num;j++)
		{
			int a,c;
			scanf("%d%d",&a,&c);
			add(i,a,c);
		}
	}
	for(int i=n-m+1;i<=n;i++)
	{
		scanf("%d",&cost[i]);
		dp[i][1] = cost[i];//叶子节点选择自己的值
	}
	for(int i=1;i<=n;i++) dp[i][0] = 0;//所有节点在不选择任何节点的情况下都是0
	dfs(1);
	for(int i=m;i>=0;i--)
	{
		if(dp[1][i] >= 0)
		{
			printf("%d",i);
			break;
		}
	}
	return 0;
}
posted @ 2024-03-29 17:22  风丨铃  阅读(5)  评论(0编辑  收藏  举报