poj 2057 && 1947

两道树形 dp

题目:http://poj.org/problem?id=2057

题意:蜗牛的房子丢在了树上的某个节点上(每个节点的可能性相同),有些节点上有虫子,可以告诉蜗牛它的房子是不是在以这个节点为根的子树上,蜗牛找到房子的最小数学期望值是多少。

首先期望是:找到房子走的步数 / 叶子节点的总数。叶子节点总数是固定的,那么走的步数越少,期望值也就越小,因为一个节点可能有多个孩子,从不同的孩子开始走得到的步数是不同的,优先选择哪个孩子这里用了贪心,解释见代码。定义三个数组

su[N]  // su [i] 表示走到以 节点 i 为根的子树成功找到房子的步数 如果 i 是叶子节点那么 su [i] = 0

 fa[N] // fa [i] 表示走到以节点 i 为根的子树找不到房子的步数 如果 i 为叶子节点 或 worm[i] == 0 那么 fa[i] = 0

 le[N] // le [i] 表示 i 为根的子树 叶子节点的数目 初始化所有的节点该值为零,为了递归计算其他节点,所以当遇到叶子节点是 赋值 le [i] = 1;

View Code
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <iostream>
 4 #include <algorithm>
 5 #include <vector>
 6 #include <math.h>
 7 #define N 1010
 8 #define inf 100000000
 9 #define _clr(a,val) (memset(a,val,sizeof(a)))
10 
11 using namespace std;
12 
13 typedef long long ll;
14 
15 int su[N],fa[N],le[N];
16 int worm[N];
17 int tr[N][N];
18 int n;
19 bool cmp(int x,int y)
20 {
21     return (fa[x] + 2) * le[y] < (fa[y] + 2) * le[x];
22 }
23 void dp(int x)
24 {
25     int i,j;
26     if(tr[x][0] == 0)  // x 为叶子节点
27     {
28         le[x] = 1;
29         su[x] = fa[x] = 0;
30         return ;
31     }
32     for(i = 1; i <= tr[x][0]; i++)
33     dp(tr[x][i]); 
34     for(i = 1; i <= tr[x][0]; i++)
35     {
36         le[x] += le[tr[x][i]]; // 求 以 x 节点为根的子树叶子节点数目
37         if(worm[x] == 0)
38         {
39             fa[x] += (fa[tr[x][i]] + 2);  // 在树的最底层 或 该节点有虫子时 fa[i] = 0, 当不是最低是 转移方程 fa[父节点] = fa[子节点] + 2 
40         }
41     }
42     int tem[N];  
43     for(i = 0; i < tr[x][0]; i++) // 贪心
44     {
45         tem[i] = tr[x][i + 1];  
46     }
47     sort(tem,tem + tr[x][0],cmp);  // 排序
48     for(i = 1,j = 0; i <= tr[x][0]; i++)
49     {
50         su[x] += ((j + 1) * le[tem[i - 1]] + su[tem[i - 1]]); // su的转移方程(这个方程的解释  http://blog.sina.com.cn/s/blog_5f5353cc0100hd08.html ),从这里可以看出,找到房子的步数 取决于 (fa[u] + 2) * le[v],所以贪心的时候按这个从小到大排序,然后依次选择
51         j += (fa[tem[i - 1]] + 2);
52     }
53 }
54 int main()
55 {
56     int i,x;
57     char ch;
58     //freopen("data.txt","r",stdin);
59     while(scanf("%d",&n),n)
60     {
61         _clr(tr,0);
62         for(i = 1; i <= n; i++)
63         {
64             worm[i] = 0;
65             scanf("%d %c",&x,&ch);
66             if(x != -1)
67             {
68                 tr[x][++tr[x][0]] = i; 
69             }
70             if(ch == 'Y')
71             {
72                 worm[i] = 1;
73             }
74             su[i] = fa[i] = le[i] = 0;
75         }
76         dp(1);
77         double ans = su[1] * 1.0 / (le[1] * 1.0);
78         printf("%.4lf\n",ans);
79     }
80     return 0;
81 }

题目:http://poj.org/problem?id=1947

题意:给一颗 n 个节点的树,问去掉最少的边可以得到一颗子树,而且该子树含有 p 个节点

dp[i][j] 表示 在以 i 为根节点的子树中 j 个节点时 去掉的最少边数 转移方程   dp[x][j] = min(dp[x][j],dp[map[x][i]][k] + dp[x][j - k] - 2),关于方程里 - 2 的一种解释: 我们把以 x 为根的节点的子树,把每一个分支作为背包的物品,决策就是每一个分支的选与不选,而对于每一个分支的状态其实就是该问题的一个子问题,然后这样分割成 2 块后,我们会发现多砍了该节点与子节点的边两次,要减去之 。求解这个 dp[i][j] 用典型的 0 - 1背包形式去求

View Code
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <iostream>
 4 #include <algorithm>
 5 #include <vector>
 6 #include <math.h>
 7 #define N 160
 8 #define inf 100000000
 9 #define _clr(a,val) (memset(a,val,sizeof(a)))
10 
11 using namespace std;
12 
13 typedef long long ll;
14 
15 int fa[N];
16 int dp[N][N];
17 int map[N][N];
18 int n,p;
19 void dfs(int x)
20 {
21     int i,j,k;
22     for(i = 1; i <= map[x][0]; i++)
23     dfs(map[x][i]);
24     for(i = 1; i <= map[x][0]; i++)
25     {
26         for(j = p; j > 1; j--)
27         {
28             for(k = 1; k < j; k++)
29             dp[x][j] = min(dp[x][j],dp[map[x][i]][k] + dp[x][j - k] - 2);
30         }
31     }
32 }
33 int main()
34 {
35     int i,j;
36     int x,y;
37     //freopen("data.txt","r",stdin);
38     while(scanf("%d%d",&n,&p) != EOF)
39     {
40         _clr(map,0);
41         for(i = 0; i < N; i++)
42         for(j = 0; j < N; j++)
43         dp[i][j] = inf;
44         _clr(fa,-1);
45         for(i = 0; i < n - 1; i++)
46         {
47             scanf("%d%d",&x,&y);
48             map[x][++map[x][0]] = y;
49             fa[y] = x;
50         }
51         for(i = 1; i <= n; i++)
52         dp[i][1] = map[i][0] + 1;
53         for(i = 1; i <= n; i++)
54         if(fa[i] == -1) break;
55         dp[i][1] --;
56         dfs(i);
57         int ans = inf;
58         for(i = 1; i <= n; i++)
59         {
60             ans = min(ans,dp[i][p]);
61         }
62         printf("%d\n",ans);
63     }
64     return 0;
65 }

 

原文地址:https://www.cnblogs.com/fxh19911107/p/2636481.html