题目大意:
给你一棵树,问要切割出节点数为m的子树,至少需要多少次切割;
解题思路:
我们考虑i结点为根结点,那么切割出j个结点需要的最少的次数;
dp[i][j]表示以i为根结点,切割出j个结点的子树(该子树包含i结点),那么对与每一个根结点,dp[i][1]我们不考虑它的父亲结点,则需要size【i】次,这里size是它的直接相连的子孙个数,注意直接相连;
对于状态dp[i][j],我们要考虑与它的子孙v的切割状态,切割掉子树得到dp【i】[j]状态,不切割子树则为dp[i][k]+dp[v][j-k]-1,为什么要减1呢?dp【i】[k]肯定不包含dp[v][j-k],如果包含的话,它的结点数就要大于k了,所以dp【i】[k】保存的状态是切掉了dp[v][j-k]的,但是现在要加上后者,所以要少切1次;
因此状态转移方程就成为了dp[i][j]=min(dp[i][j],dp[i][k]+dp[v][j-k]);
最后在找答案的时候,因为我们是按每个点为根结点考虑的,因此在取实际结果的时候,除了深搜开始的根节点,其他结点的答案都要加1,因为要切开与父亲结点的连接;
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200
int cnt,head[maxn];
int dp[maxn][maxn],size[maxn];
int n,m;
struct Edge{
int to,next;
}edge[2*maxn];
void init()
{
cnt=0;
memset(head,-1,sizeof(head));
memset(dp,0x3f3f3f3f,sizeof(dp));
memset(size,0,sizeof(size));
}
void addedge(int u,int v)
{
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void tree_dp(int u,int f)
{
dp[u][1]=size[u];
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(v!=f)
{
tree_dp(v,u);
for(int j=m;j>=1;j--)
{
for(int k=1;k<=j;k++)
dp[u][j]=min(dp[u][j],dp[u][k]+dp[v][j-k]-1);
}
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
addedge(a,b);
size[a]++;
}
tree_dp(1,-1);
int ans=dp[1][m];
for(int i=1;i<=n;i++)
ans=min(ans,dp[i][m]+1);
printf("%d\n",ans);
}
}