LightOJ - 1287 Where to Run?(dfs+概率dp)

题目链接:Where to Run? - LightOJ 1287 - Virtual Judge (ppsucxtt.cn)

题意:有n个城市,编号为0~n-1,有m条有权无向边,边权代表经过这条边所要花费的时间,有个小偷从编号为0的城市出发,他不会走已经走过的城市,每到一个城市,他都会进行选择,要么选择在该城市停留5分钟,要么就前往下一个城市,他所前往的下一个城市具备以下性质:

(1)在该城市可以不经过已经经过的城市且把剩余未经过的城市全部遍历

(2)选择任何一个满足性质的城市的概率是相同的,且与小偷在原城市停留的概率是相同的

分析:这道题显然是一个概率dp,由于要遍历所有的城市,所以我们一定要记录下当前已经遍历的城市的集合,且还要知道当前所在的城市,所以我们就能够想到用状压dp去求解。

dp[i][j]表示小偷当前在城市i且已经遍历的城市的状态是j时遍历所有城市所要花费的时间期望,我们可以先预处理出来在城市i且已经遍历的城市的状态是j的情况下小偷可以去的城市数cnt[i][j],则小偷下一步可能去每个城市的概率都是1/(1+cnt[i][j]),小偷在当前城市停留的概率也是1/(1+cnt[i][j]),那么就有下面的概率dp分析:

 概率dp转移方程求出来了,现在就差求出来在cnt[i][j]了,这个是用记忆化搜索预处理出来的,代码中有详细注释,我就不赘述了。大家可以参考下代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=16;
int h[N],ne[N*N],e[N*N],w[N*N],idx;
int cnt[N][1<<N];//cnt[i][j]表示当前位置在i,且状态为j时可选的道路数 
int f[N][1<<N];//用于求cnt数组设置的记忆化数组
double dp[N][1<<N];//dp[i][j]表示当前位置在i,且状态为j时遍历完城市所需要的时间期望值 
int n,m,x,y,z,End;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
int dfs(int x,int State)
{
	if(State==End) return f[x][State]=1;//不能直接返回1,要更新终止态的值,在DP中会用到 
	if(f[x][State]) return f[x][State];
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		//走已经走过的城市,或如果当前走第j个城市则无法走遍所有的城市都要被跳过 
		if((State>>j&1)||!dfs(j,State|(1<<j))) continue;
		f[x][State]=1;//下一步走第j个城市可以达到走遍所有城市的目的 
		cnt[x][State]++;//当前在第x个城市且状态为j时,下一步可以走的城市数目+1 
	}
	return f[x][State];
}
double DP(int x,int State)
{
	if(State==End||cnt[x][State]==0) return 0;//到达终止状态或者不满足题意的状态都要返回0 
	if(dp[x][State]) return dp[x][State];
	double p=1.0/(cnt[x][State]+1);
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if((State>>j&1)||!f[j][State|(1<<j)]) continue;
		dp[x][State]+=(DP(j,State|(1<<j))+w[i])*p;
	}
	dp[x][State]+=5*p;
	dp[x][State]/=(1-p);
	return dp[x][State];
}
int main()
{
	int T;
	cin>>T;
	for(int o=1;o<=T;o++)
	{
		memset(h,-1,sizeof h);
		memset(f,0,sizeof f);
		memset(cnt,0,sizeof cnt);
		memset(dp,0,sizeof dp); 
		scanf("%d%d",&n,&m);
		idx=0;End=(1<<n)-1;
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);add(y,x,z);
		}
		dfs(0,1);
		printf("Case %d: %.10lf\n",o,DP(0,1));
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值