题意:给出一颗n个节点的树,这道题数据都是以1为根; 

    给出一个p,表示留下p个节点,要我们求出留下p个节点删除边的数量最少是多少

思路:树形dp  dp【i】【j】

      表示i这颗子树保留j个节点所需要删除边的数量最少的权值

        那么dp【i】【1】就是把他所有儿子全部去掉,那么就是他son【】的个数

          求出来后,会发现,这个i节点可能还有父亲,所以,在计算最后权值的时候,需要再+1;

      那么,如何算呢?

        我们采用dfs的方式,遍历到节点i的时候,假如计算出他的儿子节点k

          那么就将儿子的节点数加到此节点上,然后开始枚举最优值

代码如下:

P1272 重建道路_i++P1272 重建道路_i++_02
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e3+10;
 4 const int inf=0x3f3f3f3f;
 5 struct node
 6 {
 7     int v,nxt;
 8 }G[maxn];
 9 int dp[maxn][maxn];   //表示在以i为根的情况下,保留j个节点需要的最少操作
10                     //不过这里没有考虑其父亲,所以最后需要ans+1;
11 int son[maxn];
12 int head[maxn];int num;
13 int sum[maxn];
14 void add(int u,int v)
15 {
16     G[++num].v=v;G[num].nxt=head[u];head[u]=num;
17 }
18 void dfs(int u)
19 {
20     sum[u]=1;
21     for(int i=head[u];i;i=G[i].nxt){
22         int v=G[i].v;
23         dfs(v);
24         sum[u]+=sum[v];  //记录个数
25         for(int i=sum[u];i>=0;i--){
26             for(int j=1;j<i;j++){     //要在这颗子树上枚举,自然从1开始
27                                       //为何要小于i?
28                                     //因为这两棵树要连在一起,肯定要有父亲的边
29                                     //所以不能所有的边都从这颗子树上拿
30                 //但是假如枚举的边的数量超过子节点的数量怎么办呢?
31                 //没事,我们赋值为无穷大了,倘若枚举到这样一个数,也无法转移状态
32                 dp[u][i]=min(dp[u][i],dp[v][j]+dp[u][i-j]-1);
33             }
34         }
35     }
36 }
37 int main()
38 {
39     int n,p;
40     scanf("%d%d",&n,&p);
41     for(int i=1;i<n;i++){
42         int u,v;
43         scanf("%d%d",&u,&v);
44         add(u,v);
45         son[u]++;  //记录儿子数量
46     }
47     memset(dp,inf,sizeof(dp));
48     for(int i=1;i<=n;i++) dp[i][1]=son[i];  //此子树只保留根节点时需要删除的边数
49     dfs(1);
50     int ans=dp[1][p];  //1为根节点,没父亲
51     for(int i=2;i<=n;i++)        
52         ans=min(ans,dp[i][p]+1);  //其他节点有父亲,假如以他为答案,就需要减去其父亲
53     printf("%d\n",ans);
54     return 0;
55 }
View Code