题目链接:https://vjudge.net/problem/POJ-1947
题意:给定一棵树,求得到一个结点数为p最少删多少条边。
思路:
明显的树形dp,分组背包。用dp[u][j]表示在结点u的子树上选j个结点最少要删除的边(一定包含结点u),那么dp[u][1]=num[u],num[u]表示结点u的子结点个数,然后转移方程为:
dp[u][j]=min(dp[u][j] , dp[u][j-k]+dp[v][k]-1),v是u的子结点,k表示在v的子树中选k个结点,-1是因为选择了v那么u->v的边不用删了。
最后的结果是min(dp[1][p],dp[i][p]+1) (i>=2),因为如果不为根节点,还要删除i与其父结点的边。
AC代码:
#include<cstdio> #include<algorithm> using namespace std; const int maxn=155; const int inf=0x3f3f3f3f; int n,p,ans,cnt,head[maxn],son[maxn],num[maxn],dp[maxn][maxn]; struct node{ int v,nex; }edge[maxn]; void adde(int u,int v){ edge[++cnt].v=v; edge[cnt].nex=head[u]; head[u]=cnt; } void dfs(int u){ son[u]=1; dp[u][1]=num[u]; for(int i=head[u];i;i=edge[i].nex){ int v=edge[i].v; dfs(v); son[u]+=son[v]; for(int j=son[u];j>0;--j) for(int k=1;k<=min(j-1,son[v]);++k) dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1); } } int main(){ while(~scanf("%d%d",&n,&p)){ cnt=0; for(int i=1;i<=n;++i) head[i]=num[i]=0; for(int i=1;i<n;++i){ int u,v; scanf("%d%d",&u,&v); adde(u,v); ++num[u]; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dp[i][j]=inf; dfs(1); ans=dp[1][p]; for(int i=2;i<=n;++i) if(son[i]>=p) ans=min(ans,dp[i][p]+1); printf("%d ",ans); } return 0; }