【POJ 1947】Rebuilding Roads(树型DP)

本文讨论了如何利用树形动态规划解决重建道路问题,包括时间限制、内存限制等参数设定,以及如何确定最少破坏多少条道路才能使特定数量的节点孤立。通过实例分析,解释了树型DP的基本思路和实现过程。

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

Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 10607 Accepted: 4863

Description

The cows have reconstructed Farmer John's farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The cows didn't have time to rebuild any extra roads, so now there is exactly one way to get from any given barn to any other barn. Thus, the farm transportation system can be represented as a tree.

Farmer John wants to know how much damage another earthquake could do. He wants to know the minimum number of roads whose destruction would isolate a subtree of exactly P (1 <= P <= N) barns from the rest of the barns.

Input

* Line 1: Two integers, N and P

* Lines 2..N: N-1 lines, each with two integers I and J. Node I is node J's parent in the tree of roads.

Output

A single line containing the integer that is the minimum number of roads that need to be destroyed for a subtree of P nodes to be isolated.

Sample Input

11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11

Sample Output

2

Hint

[A subtree with nodes (1, 2, 3, 6, 7, 8) will become isolated if roads 1-4 and 1-5 are destroyed.]

Source


卡了一阵子……只能说对树型dp的理解还不是非常深刻……

题目大意:一棵树,问最少砍几条边,能得到一个p个节点的子树。

这样可以想到用dp[i][j]表示节点i为根,得到j个节点的子树所需要砍掉的最少边数。

这样对于父节点u,遍历孩子节点v1.2.3...时,每当新加一个子节点v,可以选取v中0.1.2.3....k(k <= v子树的节点数) 这样对于dp[u][i] 可以通过dp[u][j]外加从v中选取的节点(i-j个)来组成,也就是转移方程。


如果上面的想明白了,会发现有新的问题,dp[i][j]表示以i为根的j个节点的子树所砍掉的边数,但不能包含i和其父亲(如果i不是根)的边,否则在遍历i的父亲那层中会导致转移出错,因为在这层,它与i之间的边不可砍掉(除非整个i子树都不选取)。所以我的方法就是做了一堆啰嗦的判断……


大体这样取个最小值就行了,要注意p个节点的子树可以以原树中任何一个节点为根,也就是答案要在遍历的过程中找。


代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("in.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;

bool can[155][155];
int dp[155][155];
int n,p,mn;

void dfs(int u)
{
	memset(dp[u],INF,sizeof(int)*n);
	dp[u][0] = dp[u][1] = 0;
	bool f = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(!can[u][i]) continue;
		f = 1;
		dfs(i);
		for(int k = p; k >= 1; --k)
		{
			if(dp[u][k] != INF) dp[u][k]++;
			for(int j = 1; j <= p && j < k && dp[i][j] != INF; ++j)
			{
				if(dp[u][k-j] != INF) dp[u][k] = min(dp[u][k-j]+dp[i][j],dp[u][k]);
			}
		}
	}
	//如果u不是整个树的根,那么在dp数组中不计入其与父节点的边(为了方便转移),在取答案时需要加上(断绝它与父节点的联系)
	if(u != 1) mn = min(mn,dp[u][p]+1);
	else mn = min(mn,dp[u][p]);
}

int main()
{
	//fread();
	//fwrite();

	int u,v;
	while(~scanf("%d%d",&n,&p))
	{
		memset(can,0,sizeof(can));
		for(int i = 1; i < n; ++i)
		{
			scanf("%d%d",&u,&v);
			can[u][v] = 1;
		}
		mn = INF;
		dfs(1);
		printf("%d\n",mn);
	}

	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值