poj1947 Rebuilding Roads (树形dp)

题目链接:点击这里

题目大意:
给你一棵 n n n 个节点的树,求最少删掉几条边才能得到一颗 p p p 节点的子树

题目分析:
很明显这是一道树形dp。其状态也很容易发现: d p [ i ] [ j ] dp[i][j] dp[i][j] 为以 i i i 为根的子树分割出一颗 p p p 节点的子树需要删的边的数目。
其初始条件也很容易确定:由于 d p [ i ] [ 1 ] dp[i][1] dp[i][1] 表示只剩一个节点的子树即只剩了 i i i 点本身,所以 d p [ i ] [ 1 ] dp[i][1] dp[i][1] 其儿子节点的数目
d p dp dp 的转移方程为: d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ j − k ] + d p [ s o n ] [ k ] − 1 ) dp[i][j]=min(dp[i][j],dp[i][j-k]+dp[son][k]-1) dp[i][j]=min(dp[i][j],dp[i][jk]+dp[son][k]1)
我们感性解释一下这个方程,我们需要找到一颗以 i i i 节点为根的大小为 p p p 的子树,这颗子树可以通过保留 i i i 节点的 j − k j-k jk 个孩子,再从枚举这些孩子节点从他们中在分割出 k k k 个节点与之相连即可,上述方程的-1是因为 i i i 节点和他的儿子节点之间的边被计算了两次
具体细节见代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 155;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,p,in[maxn],dp[maxn][maxn];
vector<int> v[maxn];
void dfs(int u)
{
	for(int i = 0;i < v[u].size();i++)
	{
		dfs(v[u][i]);
		for(int j = p;j >= 1;j--)
			for(int k = 1;k < j;k++)
				dp[u][j] = min(dp[u][j],dp[u][j-k]+dp[v[u][i]][k]-1);
	}
}
int main()
{
	n = read(),p = read();
	for(int i = 1;i < n;i++)
	{
		int a = read(),b = read();
		v[a].push_back(b);
		in[b]++;
	} 
	memset(dp,0x3f,sizeof(dp));
	int root;
	for(int i = 1;i <= n;i++)
		if(!in[i])
		{
			root = i;
			break;
		}
	for(int i = 1;i <= n;i++)
		dp[i][1] = v[i].size();
	dfs(root);
	int ans = dp[root][p];
	for(int i = 1;i <= n;i++)
		if(i != root)ans = min(ans,dp[i][p]+1);//+1是因为要剪断与父亲节点的联系 
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值