题解 HDU4035 Maze
题意
有一个树形的迷宫,你从根节点1出发,每一个节点有\(k_i\)的概率死去,并重新从节点1出发;有\(e_i\)的概率逃脱迷宫,从一个点有相等的概率穿过一次通道到达其他的点,求成功逃脱的期望次数
节点数\(n\leq1e5\)
思路
我们设\(f(i)\)表示从i出发逃脱的期望次数,\(deg_i\)表示节点i的度
一看就是一个期望题嘛,我们考虑如何树形DP
由于求的时候会先算出子节点,我们将子节点与父节点分开来处理
可以得到
\[f(i)=k_if(1)+\frac{1-k_i-e_i}{deg_i}\sum_{j\in son(i)}(f(j)+1)+\frac{1-k_i-e_i}{deg_i}(f(fa_i)+1)
\]
然后这个就会互相影响没法做了
一般来说循环转移我们会去写高斯消元对不对,可是这道题你写会炸掉
这样我们考虑一下别的
观察到每一个状态转移方程中都有\(f(1)\),假设我们已经求得\(f(1)\),就可以求解出整个转移系统,且有形式化的\(f_i=A_if(1)+B_if(fa_i)+C_i\)
将\(f(j)=A_jf(1)+B_jf(fa_j=i)+C_j\)代入原式得
\[f(i)=k_if(1)+\frac{1-k_i-e_i}{deg_i}\sum_{j\in son(i)}(A_jf(1)+B_jf(i)+C_j+1)+\frac{1-k_i-e_i}{deg_i}(f(fa_i)+1)
\]
令\(D_i=\frac{1-k_i-e_i}{deg_i}\),然后按照形式整理一下式子
\[(1-d_i\sum_{j\in son(i)}b_j)f_i=(k_i+D_i\sum_{j\in son(i)}A_j)f(1)+D_if(fa_i)+D_i+D_i(\sum_{j\in son(i)}(C_j+1))
\]
令\(T_i=1-d_i\sum_{j\in son(i)}b_j\)
则
\[A_i=\frac{k_i+D_i\sum_{j\in son(i)}A_j}{T_i}
\]
\[B_i=\frac{D_i}{T_i}
\]
\[C_i=\frac{D_i+D_i(\sum_{j\in son(i)}(C_j+1))}{T_i}=\frac{D_i*deg_i+D_i(\sum_{j\in son(i)}C_j)}{T_i}
\]
在\(i=1\)时,\(f(1)=A_1f(1)+C_1\),然后\(f(1)=\frac{C_1}{1-A_1}\)
然后在树上跑DP从下往上求出\(A,B,C\)即可,注意若除数为0则无解
注意下文代码中的\(A,B,C\)与上文并非对应关系,准确来说是\(A,B\)反了
推得真辛苦
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int const MAXN=1e5;
double eps=1e-10;
int TT,n,tot,cnt;
double k[MAXN],e[MAXN],B[MAXN],A[MAXN],C[MAXN],d[MAXN],T[MAXN];
int deg[MAXN],h[MAXN];
bool flag;
struct edge{
int to,next;
}E[MAXN];
void add(int u,int v){
E[++tot]=(edge){v,h[u]},h[u]=tot,deg[u]++;
}
void dfs(int x,int fa){
T[x]=1, B[x]=k[x], A[x]=d[x], C[x]=1-k[x]-e[x];
//if(deg[x]-1==0 && x!=1)return;
for(int i=h[x];i;i=E[i].next){
int to=E[i].to;
if(to==fa)continue;
dfs(to,x);
if(flag)return;
T[x]-=d[x]*A[to];
B[x]+=d[x]*B[to];
C[x]+=d[x]*C[to];
}
if(fabs(T[x]) < eps){flag=1;return;}
B[x]/=T[x];A[x]/=T[x];C[x]/=T[x];
return ;
}
int main(){
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%d",&TT);
while(TT--){
scanf("%d",&n);
memset(h,0,sizeof(h));
memset(deg,0,sizeof(deg));
flag=0,tot=0;
for(int i=1,x,y;i<=n-1;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++){
scanf("%lf%lf",&k[i],&e[i]);
k[i]/=100.0;e[i]/=100.0;
//printf("%lf %lf\n",k[i],e[i]);
}
for(int i=1;i<=n;i++)
d[i]=(1-k[i]-e[i])/deg[i];
dfs(1,0);
printf("Case %d: ",++cnt);
//printf("%.18lf %.18lf\n",fabs(1-B[1]),eps);
if(fabs(1-B[1])<eps || flag){
printf("impossible\n");
}else{
printf("%.6lf\n",C[1]/(1.0-B[1]));
}
}
return 0;
}
后记
这道题体现出了循环转移方程的线性解法,关键在于有一个核心状态与所有状态有关,才能如此求解