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

时间:2023-03-08 16:25:57
借助树的概率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;
}