Description
给出一棵有n个节点的树,问如何减去最少的边,得到一个含有p个节点的子树
Input
第一行两个整数n和p,之后n-1行每行两个整数a和b表示a和b间有一条边
Output
输出减去最少的边数
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
Solution
令dp[root][i]表示对root为根节点的树得到一棵有i个节点的子树需要减去的边数,那么对于节点i的一个儿子son有两种情况:
1.不去掉son子树
dp[root][i]=min(dp[root][j]+dp[son][i-j]) (0<=j<=i)
2.去掉son子树
dp[root][i]=dp[root][i]+1
综合一下就是dp[root][i]=min(dp[root][j]+dp[son][i-j],dp[root][i]+1) (0<=j<=i)
Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 151
#define INF 0x3f
int n,p,fa[maxn],son[maxn],bro[maxn],dp[maxn][maxn];
void dfs(int root)
{
dp[root][1]=0;//初始化
int k=son[root];//枚举根节点的儿子们
while(k)
{
dfs(k);//深搜
for(int i=p;i>=1;i--)
{
int temp=dp[root][i]+1;
for(int j=1;j<i;j++)//转移方程
temp=min(temp,dp[root][j]+dp[k][i-j]);
dp[root][i]=temp;
}
k=bro[k];//从k的兄弟节点继续转移
}
}
int main()
{
while(~scanf("%d%d",&n,&p))
{
memset(son,0,sizeof(son));//初始化
memset(fa,0,sizeof(fa));
memset(dp,INF,sizeof(dp));
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
fa[b]=a;//b的父亲是a
bro[b]=son[a];//b的兄弟节点是a的上一个儿子
son[a]=b;//a现在的儿子是b
}
int root;
for(int i=1;i<=n;i++)//找到根节点
if(!fa[i])
{
root=i;
break;
}
dfs(root);//从根节点开始转移
int ans=dp[root][p];
for(int i=1;i<=n;i++)
ans=min(ans,dp[i][p]+1);//任何一个不是根节点的节点欲成为根节点必须先切一条边
printf("%d\n",ans);
}
return 0;
}