poj 2057 树形dp+贪心(蜗牛找家的期望值)

题意:一只小蜗牛爬树的时候从树枝的末端(也就是从叶子结点)上掉了下来,但是它的壳子还留在上面。于是它从根节点去寻找它的壳子(但是它完全忘记了之前走过的路)。在路途中有些节点上可能住着虫子,虫子可以告诉小蜗牛它之前来没来过。假如壳子在每个叶子节点上的概率相等,为蜗牛选出一条路,使得所需要走的路程的期望最小。

思路:树形dp+贪心,完全自己想出来的,开心!!

显然可以构造状态dp[x][0]表示目的地不在以x为根的子树上需要走的路程期望(相当于遍历这个子树走过的路程),dp[x][1]表示从x出发,且目的地在以x为根的子树上走的路程期望。显然如果x上有虫子,那么dp[x][0]=0;否则dp[x][0] = sum(dp[y][0]+2) , y是x的儿子;

那么求dp[x][1]的时候涉及的问题就是儿子的访问顺序,如果考虑全排列,题目给了每个结点最多有8个儿子,那么复杂度是O(8!*n) = O(40,320,000),时限3s,现在想想说不定可以。但是最好的做法当然是找出规律,贪心来选择顺序。

考虑两个连续的儿子i和j,目的地在i所在子树的期望是son(i)*k+dp[i][1](其中k是访问其他儿子没找到经过的路径),目的地在j的期望是son(j)*(k+dp[i][0]+2)+dp[j][1](+2表示从i回到x,再从x到j)。如此再写出j在前i在后的和,两相比较消去相同项得到前者是son(j)*(bi+2),后者是son(i)*(bj+2),那么如果i在前,所要求的条件就是前者小于后者,那么也就是(bi+2)/son(i)要小于(bj+2)/son(j)。这是贪心经典的交换论证方法。也就是说最优的排列应该按照(bi+2)/son(i)的大小来进行排序。

最后,输出dp[1][1]/son[1]即可。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 1001
bool ch[N];
int dp[N][2],first[N],top,son[N];
int n;
struct edge{
    int y,next;
}e[N];
struct node{
    int a,b,son;
};
void add(int x,int y){
    e[top].y = y;
    e[top].next = first[x];
    first[x] = top++;
}
int cmp(node x,node y){
    return (x.b+2)*y.son < (y.b+2)*x.son;
}
void dfs(int x){
    int i,j,k,y;
    struct node p[N];
    j = 0;
    dp[x][0] = dp[x][1] = 0;
    if(first[x] == -1){
        son[x] = 1;
        return;
    }
    son[x] = 0;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        dfs(y);
        dp[x][0] += dp[y][0]+2;
        son[x] += son[y];
        p[j].a = dp[y][1];
        p[j].b = dp[y][0];
        p[j++].son = son[y];
    }
    if(ch[x])
        dp[x][0] = 0;
    sort(p,p+j,cmp);
    for(i = 0,k=1;i<j;i++){
        dp[x][1] += p[i].son*k+p[i].a;
        k += p[i].b+2;
    }
}
int main(){
    while(scanf("%d",&n) && n){
        int i,j;
        char str;
        top = 0;
        clr(first, -1);
        clr(ch, false);
        for(i = 1;i<=n;i++){
            scanf("%d %c",&j,&str);
            if(i>1)
                add(j,i);
            if(str == 'Y')
                ch[i] = true;
        }
        dfs(1);
        printf("%.4lf\n",(double)dp[1][1]/son[1]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值