题意:
给出一颗树,剪去一些边将它变成一个含有m个结点的树;
求减去的最小边数;
1<=m<=n<=150;
题解:
显然是树形dp,考虑状态要包括当前结点信息和子树大小;
就设状态f[ x ][ j ]为在以x为根的子树上,取包括x的j个点,所需要剪掉的边数;
对于x的子结点y,深搜回溯之后的转移为:
f[ x ][ j ]=min(f[ x ][ j-k ]+f[ y ][ k ]);
0<=k<=size[y]
然而在x有多个子结点的时候会造成前一个dp值过小而无法转移;
这时候应该将f[ x ][ j ]+1之后再转移,因为相对于前一个,剪边数最多+1(即将新结点剪掉);
这道题的状态还是很好想的,就是边界和转移比较坑;
还有最后的答案除了根结点以外,都要+1,因为要从父结点上剪掉这个树;
代码:
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 155
using namespace std;
vector<int>to[N];
int size[N],f[N][N],n,m,ans;
void dfs(int x,int pre)
{
size[x]=1;
f[x][0]=1;
f[x][1]=0;
int i,j,k,y;
for(i=0;i<to[x].size();i++)
{
if((y=to[x][i])!=pre)
{
dfs(y,x);
size[x]+=size[y];
for(j=size[x];j>=1;j--)
{
f[x][j]++;
for(k=0;k<=size[y]&&k<j;k++)
{
f[x][j]=min(f[x][j-k]+f[y][k],f[x][j]);
}
}
}
}
if(size[x]>=m)
ans=min(ans,f[x][m]+1);
}
int main()
{
int i,j,k,x,y;
scanf("%d%d",&n,&m);
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
to[x].push_back(y);
to[y].push_back(x);
}
ans=0x3f3f3f3f;
memset(f,0x3f,sizeof(f));
dfs(1,0);
ans=min(ans,f[1][m]);
printf("%d",ans);
return 0;
}