题目大意:给你一棵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; }