题目大意:给你一棵n个节点的树,现在要你删除尽可能少的边,使得剩余一个节点数刚好为k的子树。你需要输出节点数和删除的边的编号。
解题思路:树形dp。
设dp[i][j]和v[i][j]表示以i为根的子树中删除j个节点最少删的边数,和其所需删除的边对应的(点,删除的节点个数),用一个pair存储。
那么转移状态的时候类似于背包问题。
dp[i][j]=min{dp[s][k]+dp[i][j-k]}(s为i的儿子)。
更新答案的同时暴力更新v即可。
最后搜索根,注意如果根不为1,则需要把根与它的父亲的连边也去掉,即答案要+1。
时间复杂度$O(n^3)$。
C++ Code:
#include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
#define N 405
using namespace std;
vector<pair<int,int> >v[N][N],G[N];
int n,k,fa[N],sz[N],dp[N][N];
inline int readint(){
char c=getchar();
for(;!isdigit(c);c=getchar());
int d=0;
for(;isdigit(c);c=getchar())
d=(d<<3)+(d<<1)+(c^'0');
return d;
}
void dfs(int now,int pre){
sz[now]=1;
for(int i=0,s=G[now].size();i<s;++i){
int to=G[now][i].first;
if(to!=pre){
dfs(to,now);
fa[to]=G[now][i].second;
sz[now]+=sz[to];
}
}
}
void dfs2(int now,int pre){
if(now==1)dp[now][sz[now]]=0;else{
dp[now][sz[now]]=1;
v[now][sz[now]].clear();
v[now][sz[now]].push_back(make_pair(now,sz[now]));
}
dp[now][0]=0;
for(int i=0,s=G[now].size();i<s;++i){
int to=G[now][i].first;
if(to!=pre){
dfs2(to,now);
for(int j=sz[now];j;--j)
for(int k=0;k<=sz[to]&&k<=j;++k)
if(dp[now][j]>dp[to][k]+dp[now][j-k]){
dp[now][j]=dp[to][k]+dp[now][j-k];
v[now][j]=v[now][j-k];
v[now][j].push_back(make_pair(to,k));
}
}
}
}
void print(int now,int p){
if(sz[now]==p){
printf("%d ",fa[now]);
return;
}
for(int i=0,s=v[now][p].size();i<s;++i)
print(v[now][p][i].first,v[now][p][i].second);
}
int main(){
n=readint(),k=readint();
for(int i=1;i<n;++i){
int x=readint(),y=readint();
G[x].push_back(make_pair(y,i));
G[y].push_back(make_pair(x,i));
}
memset(dp,0x3f,sizeof dp);
fa[1]=0;
dfs(1,0);
dfs2(1,0);
int ans=dp[1][n-k],rt=1;
for(int i=2;i<=n;++i)
if(sz[i]>=k&&dp[i][sz[i]-k]+1<ans){
ans=dp[i][sz[i]-k]+1;
rt=i;
}
printf("%d\n",ans);
if(rt!=1)printf("%d ",fa[rt]);
print(rt,sz[rt]-k);
return 0;
}