poj 1947(树形背包问题)

本文介绍了一种使用树形动态规划解决特定问题的方法,即给定一棵树,求取包含p个节点的子树时,需要切割的边的最小数量。通过定义状态转移方程,采用递归方式计算每个节点作为根节点时的最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给定一棵树,求获取一棵节点数为p的子树需要切割的边的最小数量

做法:定义数组dp[i][j]为以i为根节点获取节点数为j的树需要切割的最小的边的数量。对于i的一个子节点v,如果不要以v为根节点的整棵子树,那么dp[i][j]=dp[i][j]+1(因为要切割i与v的相连的边)。否则,可以枚举以v为根节点的子树的大小,假设为W,那么dp[i][j]=min(dp[i][j].dp[i][j-w]+dp[v][w])。

一些小细节:如果是开二维数组来做,像上面一样的,那么对于当前的根节点i,在枚举第二维的时候需要逆序枚举(就像01背包的做法)。还有,dp[i][1]一开始要初始化为0,dp[i][sum[i]]=0(sum[i]是i节点为根节点的整棵子树的大小。


#define _CRT_SECURE_NO_WARNINGS
#include<algorithm>
#include<iostream>
#include<ctime>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<climits>
using namespace std;

const int maxn = 155;
const int inf = INT_MAX - 5;
int fir[maxn], vv[maxn], nxt[maxn], e;
int sonnum[maxn], dp[maxn][maxn], desc[maxn];

void add(int a, int b)
{
	vv[e] = b;
	nxt[e] = fir[a];
	fir[a] = e++;
}

void dfs(int cur)
{
	desc[cur] = 1;
	for (int i = fir[cur]; i != -1; i = nxt[i])
	{
		int v = vv[i]; 
		dfs(v);
		desc[cur] += desc[v];
	}
}

void dfs1(int cur)
{
	dp[cur][1] = 0;
	dp[cur][desc[cur]] = 0;
	int sonind = 0;
	for (int i = fir[cur]; i != -1; i = nxt[i])
	{
		sonind++;
		int v = vv[i]; 
		dfs1(v);
		for (int j = desc[cur]; j >=1; j--)
		{
			if (dp[cur][j] != inf) dp[cur][j] = dp[cur][j] + 1;//cut the edge to this son
			for (int k = 1; k <= desc[v]; k++)
			{
				if (k >= j) break;
				if (dp[cur][j - k]!=inf && dp[v][k]!=inf)
					dp[cur][j] = min(dp[cur][j], dp[cur][j - k] + dp[v][k]);
			}
		}
	}
}
int main()
{
	int n, p;
	while (scanf("%d%d", &n, &p) != EOF)
	{
		e = 0;
		for (int i = 1; i <= n; i++)
		{
			fir[i] = -1;
		}
		for (int i = 1; i <= n; i++)
		{
			for (int k = 0; k <= n; k++)
			{
				dp[i][k] = inf;
			}

		}
		memset(sonnum, 0, sizeof(sonnum));
		memset(desc, 0, sizeof(desc));
		int a, b;
		for (int i = 1; i <= n - 1; i++)
		{
			scanf("%d%d", &a, &b);
			add(a, b);
			sonnum[a]++;
		}

		dfs(1);
		dfs1(1);
		int ans = INT_MAX;
		ans = min(dp[1][p],ans);
		for (int i = 2; i <= n; i++)
		{
			ans = min(ans, dp[i][p] + 1);
		}
		printf("%d\n", ans);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值