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;
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背包形式去求
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 }