借助树的概率dp(期望)+数学-好题-hdu-4035-Maze

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4035

题目意思:

有n个房间,有n-1条通道连接这n个房间(每两个房间之间有且只有一条路,所以实际上就是一棵树),在每个房间中,有三种选择要么被杀则回到第一个房间,概率为ki,要么从出口逃离概率为ei,要么通过通道到达其他的房间。

解题思路:

好题。

状态转移方程很好想,但是求的时候有技巧,不能直接用高斯消元来求(n有10000)肯定会超时。发现知,此题是在一棵树上转移,所以可以借助树的特点,分成两部分儿子和父亲,抽象出系数,然后从叶子节点向上求出父亲节点。

以下文字选自http://blog.csdn.net/morgan_xww/article/details/6776947

设 E[i]表示在结点i处,要走出迷宫所要走的边数的期望。E[1]即为所求。
    
    叶子结点:
    E[i] = ki*E[1] + ei*0 + (1-ki-ei)*(E[father[i]] + 1);
         = ki*E[1] + (1-ki-ei)*E[father[i]] + (1-ki-ei);
    
    非叶子结点:(m为与结点相连的边数)
    E[i] = ki*E[1] + ei*0 + (1-ki-ei)/m*( E[father[i]]+1 + ∑( E[child[i]]+1 ) );
         = ki*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei)/m*∑(E[child[i]]) + (1-ki-ei);
    
    设对每个结点:E[i] = Ai*E[1] + Bi*E[father[i]] + Ci;
    
    对于非叶子结点i,设j为i的孩子结点,则
    ∑(E[child[i]]) = ∑E[j]
                   = ∑(Aj*E[1] + Bj*E[father[j]] + Cj)
                   = ∑(Aj*E[1] + Bj*E[i] + Cj)
    带入上面的式子得
    (1 - (1-ki-ei)/m*∑Bj)*E[i] = (ki+(1-ki-ei)/m*∑Aj)*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei) + (1-ki-ei)/m*∑Cj;
    由此可得
    Ai =        (ki+(1-ki-ei)/m*∑Aj)   / (1 - (1-ki-ei)/m*∑Bj);
    Bi =        (1-ki-ei)/m            / (1 - (1-ki-ei)/m*∑Bj);
    Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj);
    
    对于叶子结点
    Ai = ki;
    Bi = 1 - ki - ei;
    Ci = 1 - ki - ei;
    
    从叶子结点开始,直到算出 A1,B1,C1;
    
    E[1] = A1*E[1] + B1*0 + C1;
    所以
    E[1] = C1 / (1 - A1);
    若 A1趋近于1则无解...

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#include<ctime>
#define eps 1e-9
#define INF 0x3fffffff
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define Maxn 10100

int deg[Maxn],cnt,n;
double A[Maxn],B[Maxn],C[Maxn]; //分别表示系数
double k[Maxn],e[Maxn];//表示被杀和逃离概率

struct Edge
{
    int v;
    struct Edge * next;
}edge[Maxn<<1],*head[Maxn<<1];

void add(int a,int b)
{
    ++cnt;
    deg[a]++; //度数
    edge[cnt].v=b,edge[cnt].next=head[a];
    head[a]=&edge[cnt];
}
void dfs(int cur,int pre)
{
    struct Edge * p=head[cur];
    bool flag=true;
    double suma=0,sumb=0,sumc=0;

    while(p)
    {
        int v=p->v;
        if(v!=pre)
        {
            flag=false;  //不是叶子节点
            dfs(v,cur);suma+=A[v];sumb+=B[v];sumc+=C[v];
        } //求出儿子节点的各和
        p=p->next;
    }
    if(flag) //叶子节点
    {
        double t=1-k[cur]-e[cur];
        A[cur]=k[cur],B[cur]=t,C[cur]=t;
        return ;
    }//非叶子节点
    double temp=(1-k[cur]-e[cur])/deg[cur];
    double tt=1-temp*sumb;

    A[cur]=(k[cur]+temp*suma)/tt;
    B[cur]=temp/tt;
    C[cur]=(temp*sumc+(1-k[cur]-e[cur]))/tt;
    return ;
}

int main()
{
    int t;

    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%d",&n);
        memset(head,NULL,sizeof(head));
        memset(deg,0,sizeof(deg));
        cnt=0;

        for(int i=1;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b),add(b,a);
        }
        for(int i=1;i<=n;i++)
        {
            double a,b;
            scanf("%lf%lf",&a,&b);
            k[i]=a/100.0,e[i]=b/100.0;
        }
        dfs(1,-1);
        printf("Case %d: ",ca);
        if(fabs(1-A[1])<eps) //这里卡精度
            printf("impossible\n");
        else
            printf("%.5f\n",C[1]/(1.0-A[1]));
    }
   return 0;
}





你可能感兴趣的:(动态规划)