很简单的一道树形DP,把我搞得太纠结了。。。。
我也知道需要把子树的情况进行背包,不过不知道该怎样写,看了别人的代码,也能明白,就是自己那个时候怎么没想起来呢。。。
题意:给一个包含n个节点的树,然后让你找一颗节点数为p的子树,同时让你删掉最少数目的边把这个子树给孤立起来,问这个最少的边数。
思路:很容易想到要用到01背包,要把子树的情况进行背包。用dp[root][j]记录 以root为根的、节点数为j的子树的孤立起来需要删除的最少的边数。
状态方程为:dp[root][p]=min(dp[root][p], dp[u][k]+dp[root][p-k]-2);(其中u为root的一个孩子)
由于u与root之间的边连接了起来,所以dp[u][k]+dp[root][p-k] 多加了2次他们之间的边,所以要减去2;
含义是:我们把以 root 为根的节点的子树,把每一个分支作为背包的物品,决策就是每一个分支的选与不选,
而对于每一个分支的状态其实就是该问题的一个子问题,然后这样分割成 2 块后,我们会发现多砍了该节点与子节点的边两次,要减去之;
代码:
View Code
1 # include<stdio.h>
2 # include<string.h>
3 # define N 155
4 int n,p;
5 struct node{
6 int from,to,next;
7 }edge[2*N];
8 int head[N],tol,ans[N],dp[N][N];
9 void add(int a,int b)
10 {
11 edge[tol].from=a;edge[tol].to=b;edge[tol].next=head[a];head[a]=tol++;
12 }
13 int min(int a,int b)
14 {
15 return a<b?a:b;
16 }
17 void dfs(int root,int father)
18 {
19 int i,j,k,u;
20 for(i=head[root];i!=-1;i=edge[i].next)
21 {
22 u=edge[i].to;
23 if(u!=father) dfs(u,root);
24 }
25 for(i=head[root];i!=-1;i=edge[i].next)
26 {
27 u=edge[i].to;
28 if(u==father) continue;
29 for(j=p;j>1;j--)
30 {
31 for(k=1;k<j;k++)
32 dp[root][j]=min(dp[root][j],dp[u][k]+dp[root][j-k]-2);//子树和父亲节点之间的边多加了两次,所以要减去
33 }
34 }
35 }
36 int main()
37 {
38 int i,j,a,b,Min;
39 while(scanf("%d%d",&n,&p)!=EOF)
40 {
41 memset(head,-1,sizeof(head));
42 memset(ans,0,sizeof(ans));
43 tol=0;
44 for(i=1;i<n;i++)
45 {
46 scanf("%d%d",&a,&b);
47 add(a,b);
48 add(b,a);
49 ans[a]++;
50 ans[b]++;
51 }
52 for(i=1;i<=n;i++)
53 for(j=1;j<=p;j++)
54 dp[i][j]=N;
55 for(i=1;i<=n;i++)
56 dp[i][1]=ans[i];
57 if(n==p) printf("0\n");
58 else
59 {
60 dfs(1,0);
61 Min=N;
62 for(i=1;i<=n;i++)
63 Min=min(Min,dp[i][p]);
64 printf("%d\n",Min);
65 }
66 }
67 return 0;
68 }