POJ-2057 The Lost House 贪心在动态规划中的应用

这题题意在代码中有解释, 求的最优值初看不太好理解. 求的是给定一个决策下的找到房子的最优期望值. 那么这题其实要做的就是一件事那就是给所有的分叉口的选择拍一个次序出来.根据这个次序我们就能够给出这只蜗牛在碰到有所情况下的唯一的一条爬行路线. 题目就是当我们安排好这样的这个次序后, 假设房子在各个叶子节点时, 需要走的路径总长最小. 这里的每一棵子树, 在枚举的过程中总是充当着不同的角色, 当枚举到的房子在自己的子树中时, 其要考虑能够安排最快的路径让蜗牛找到. 当没有在自己的子树时又不想让其放在前面让蜗牛白忙活一场. 所以综合一看, 貌似是一个很复杂的问题. 但是这题就是在如何安排子节点访问顺序时满足一个贪心策略. 这点在代码中会有介绍. 当我们能够利用一个排序安排出足够好的序列出来的时候. 在通过动态规划, 最终由子问题最优得到了全局最优.

使用yefeng1627推荐的公式生成利器生成的动态规划方程:

 

    当 j 是 i 的子节点时,  特别的,当为 i 叶子节点时有 

 

   当 j 是 i 的子节点时, 特别的, 当 i 为叶子节点时有 

 

代码如下:

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

/*
题意:有一只蜗牛爬上树睡着之后从树上掉下来,发现后面的"房子"却丢在了树上面, 现在这
     只蜗牛要求寻找它的房子,它又得从树根开始爬起,现在要求一条路径使得其找到房子
     所要爬行的期望距离最小. 爬行距离如下计算, 题目规定每一个分支和枝末都看做是
     一个节点, 这些节点之间的距离都是1, 在分支上可能会有热心的毛毛虫, 这些毛毛虫
     会如实的告诉蜗牛他之前是否经过这条路径, 也正是因为毛毛虫, 因此询问毛毛虫的顺
     序使得这题的期望是不同的. 输入数据时给定的一个邻接关系,通过上一个节点来构图 
     同时字符 'Y'表示该点有毛毛虫, 字符'N'表示该点 
     
解法:dp[i][0]表示以该点为根的节点找不到房子时要爬行最少的距离, 这个值有什么用呢?
     这个值就是用去计算房子落在其他叶子节点时,其对爬行距离的负面效应有多大.
     dp[i][1]表示以该点为根的节点在选择好所有分支点的爬行方案后,枚举完房子落在该
     子树所有叶子节点上的总爬行距离的最小值,这是一个值告诉我们这棵子树对爬行距离
     的正面效应有多大.那么有动态方程:  ch[x]表示x节点一共有多少个孩子节点 
     
     dp[i][0] = sum{ dp[j][0] + 2 } 当i没有毛毛虫且要求j是i的孩子节点,这个是很好
     理解的, 多出来的2就是连接该孩子节点的边来回的两次 
     dp[i][0] = 0 当该点有毛毛虫的时候, 原因是因为毛毛虫会告诉我们这点下面没有房子
     当一个节点时叶子节点的时候,那么 dp[i][0] = dp[i][1] = 0; 
     
     要明确dp[i][1]是表示在遇到分支选择的先后顺序决定后,我们枚举房子在其各个叶子
     上的所要爬行的总距离
     dp[i][1] = sum{ (sum{dp[1..j-1][0]+2}+1}*ch[j] +  dp[j][1]}, 其中j是i的孩子 
     这个方程要怎么去理解呢? 意思翻译过来就是遍历i的孩子中的j号子树所有叶子节点所
     要爬行的最短距离.其值就是: 
     前面[1, j-1]号子树没有找到节点所爬行的最短距离加上走了的多余的边再加上遍历j
     号子树所有叶子节点所爬行的最短距离, 那么这里显然谁前谁后就会决定最后值的大小
     
     现在只考虑序列中任意两棵子树A,B, 如果A放前面的话, 枚举完所有房子所在位置后的总距离就是
     ans1 = (ch[A] + dp[A][1]) + ((dp[A][0]+2)*ch[B] + dp[B][1] + ch[B])
     前一个括号是假设枚举房子落在A的所有叶子上, 后面的扩后是枚举在B的所有叶子上 
     ans2 = (ch[B] + dp[B][1]) + ((dp[B][0]+2)*ch[A] + dp[A][1] + ch[A])
     
     ans1 - ans2 = (dp[A][0]+2)*ch[B] - (dp[B][0]+2)*ch[A]
*/

int N, head[1005], idx, at[1005];
int dp[1005][2], ch[1005];

struct Node {
    int v, yes, next;    
}e[1005];

void insert(int x, int y) {
    ++idx;
    e[idx].v = y, e[idx].next = head[x];
    head[x] = idx; // 建立邻接链表 
}

bool cmp(int a, int b) {
    return (dp[a][0]+2) * ch[b] < (dp[b][0]+2) * ch[a];
}

int dfs(int x) {
    if (head[x] == -1) { //说明x肯定是一个叶子节点 
        dp[x][1] = dp[x][0] = 0;
        return ch[x] = 1; // 返回时叶子节点的个数 
    }
    vector<int>v;
    for (int i = head[x]; i != -1; i = e[i].next) {
        ch[x] += dfs(e[i].v);
        v.push_back(e[i].v);
    }
    sort(v.begin(), v.end(), cmp);
    for (int i = 0; i != v.size(); ++i) {
        dp[x][1] += dp[x][0]*ch[v[i]] + dp[v[i]][1] + ch[v[i]];
        // dp[x][0] 就相当于dp方程中的sum{ dp[1..j-1][0] +2 }
        dp[x][0] += dp[v[i]][0] + 2;
    }
    if (at[x]) {
        dp[x][0] = 0;    
    }
    return ch[x];
}

int main() {
    int pre;
    char str[5]; 
    while (scanf("%d", &N), N) {
        idx = -1;
        memset(head, 0xff, sizeof (head));
        memset(dp, 0, sizeof (dp));
        memset(ch, 0, sizeof (ch));
        scanf("%d %s", &pre, str); // 这两个的值已经毫无意义了 
        for (int i = 2; i <= N; ++i) {
            scanf("%d %s", &pre, str);
            at[i] = str[0] == 'Y';
            insert(pre, i);
        }
        dfs(1); // 1号节点永远是根节点
        printf("%.4lf\n", 1.*dp[1][1]/ch[1]);
    }
    return 0;    
}

 

posted @ 2013-01-16 21:54  沐阳  阅读(794)  评论(1编辑  收藏  举报