题意:有 n 个房间,由 n-1 条隧道连通起来,实际上就形成了一棵树,从结点 1 出发,开始走,在每个结点 i 都有 3 种可能:
1:被杀死,回到结点1处 (概率为ki)
2:找到出口,走出迷宫 (概率为ei)
3:和该点相连有m条边,随机走一条
求:走出迷宫需要走的边数的期望。
分析:开始没有做出来,看了题解才明白,也不能说这题目特别难吧,只是公式变形有些繁琐,概率DP有很多题都是这样,推出最初的方程之后还需要一次或多次变形。涉及的变量可能偏多,所以推的过程需要仔细一点。
设 dp[ i ] 表示从 i 点走出迷宫的期望,则
m[i] 表示与点 i 相邻的边数。 v 表示所有与点 i 相邻的点。
①:对于叶子节点
②:对于非叶子节点
可以看出来除了叶节点之外,转移方程一直在上下推,所以通常的递推法不行,我们得用到树形DP,从叶子节点往上推,根节点当然是选择节点 1 ,这样推到节点 i 的时候所有的 dp[son[i]] 都是已知的,而未知的变量是 dp[1] 和 dp[fa[i]]
那么这里我们设
并将
带入前面的式子(为了方便我们设 ),则
移项,合并同类项
令 则
这样系数都有了递推式,跑一遍树形DP就行了,所求答案就是 ...可能存在无解的情况,细想一下推过的式子只有除以0的时候才会无解,所以跑的时候及时判断标记一下就行了。
代码:
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10005;
const double eps = 1e-10;
int n,m[N];
double A[N],B[N],C[N];
double E[N],K[N],P[N];
vector<int>G[N];
bool flag;
void dfs(int u,int fa)
{
double T=0;
double Au=P[u],Bu=K[u],Cu=1-K[u]-E[u];
for(int i=0;i<G[u].size();i++)
{
if(G[u][i]==fa) continue;
int v=G[u][i];
dfs(v,u);
if(flag) return;
Bu+=B[v]*P[u];
Cu+=C[v]*P[u];
T +=A[v]*P[u];
}
T=1.0-T;
if(abs(T)<eps)
{
flag=true;
return;
}
A[u]=Au/T,B[u]=Bu/T,C[u]=Cu/T;
}
int main()
{
int T;
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
memset (A,0,sizeof (A));
memset (B,0,sizeof (B));
memset (C,0,sizeof (C));
memset (G,0,sizeof (G));
memset (m,0,sizeof (m));
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v; scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
m[u]++,m[v]++;
}
for(int i=1;i<=n;i++)
{
scanf("%lf%lf",&K[i],&E[i]);
K[i]/=100,E[i]/=100;
}
for(int i=1;i<=n;i++)
P[i]=(1-K[i]-E[i])*1.0/m[i];
flag=false;
dfs(1,0);
printf("Case %d: ",t);
if(flag||abs(1.0-B[1]<eps)) puts("impossible");
else printf("%.6lf\n",C[1]/(1.0-B[1]));
}
return 0;
}