HDU 4035(Maze)

14 篇文章 0 订阅

题意:有 n 个房间,由 n-1 条隧道连通起来,实际上就形成了一棵树,从结点 1 出发,开始走,在每个结点 i 都有 3 种可能:   

1:被杀死,回到结点1处  (概率为ki)
2:找到出口,走出迷宫    (概率为ei)
3:和该点相连有m条边,随机走一条
求:走出迷宫需要走的边数的期望。

分析:开始没有做出来,看了题解才明白,也不能说这题目特别难吧,只是公式变形有些繁琐,概率DP有很多题都是这样,推出最初的方程之后还需要一次或多次变形。涉及的变量可能偏多,所以推的过程需要仔细一点。

设 dp[ i ] 表示从 i 点走出迷宫的期望,则

dp[ i ] = ki * dp[ 1 ] + ei * 0 + \sum (dp[v]+1)*\frac{1-ki-ei}{m[i]};

m[i] 表示与点 i 相邻的边数。 v 表示所有与点 i 相邻的点。

①:对于叶子节点

dp[ i ] = ki * dp[ 1 ] + (1-ki-ei)*(dp[fa[i]]+1);

②:对于非叶子节点

dp[ i ] = ki * dp[ 1 ] + \frac{1-ki-ei}{m[i]}*\sum (dp[son[i]]+1)+\frac{1-ki-ei}{m[i]}*(dp[fa[i]]+1);

可以看出来除了叶节点之外,转移方程一直在上下推,所以通常的递推法不行,我们得用到树形DP,从叶子节点往上推,根节点当然是选择节点 1 ,这样推到节点 i 的时候所有的 dp[son[i]] 都是已知的,而未知的变量是 dp[1] 和 dp[fa[i]]  

那么这里我们设

dp[i]=A[i]*dp[fa[i]]+B[i]*dp[1]+C[i];

并将

dp[son[i]]=A[son[i]]*dp[i]+B[son[i]]*dp[1]+C[son[i]];

带入前面的式子(为了方便我们设   P[i]=\frac{1-ki-ei}{m[i]}  ),则

dp[i]=ki*dp[1]+P[i]*(dp[fa[i]]+1)+P[i]*\sum (A[son[i]]*dp[i]+B[son[i]]*dp[1]+C[son[i]]+1)

移项,合并同类项

(1-\sum A[son[i]]*P[i])*dp[i]=P[i]*dp[fa[i]]+(\sum B[son[i]]*P[i]+ki)*dp[1]+\sum (C[son[i]]+1)*P[i]+P[i];

令  T[i]=1-\sum A[son[i]]*P[i]; 则

A[i]=\frac{P[i]}{T[i]};     B[i]=\frac{\sum B[son[i]]*P[i]+ki}{T[i]};    C[i]=\frac{\sum (C[son[i]]+1)*P[i]+1-ki-ei}{T[i]};

这样系数都有了递推式,跑一遍树形DP就行了,所求答案就是  \frac{C[1]}{1.0-B[1]}  ...可能存在无解的情况,细想一下推过的式子只有除以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; 
} 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值