[NOI2005] 聪聪与可可

聪聪与可可

题目大意

给你一张图,再给你两个位置,这两个位置上有两个人,分别是A和B。A知道B的位置且每次都是他先走,A每次能走两步(由于他知道位置,所以每次都是向离B更近的点走,若距离相同,走标号最小的点),B每次随机走向下一个地点。求A和B到达同一个位置期望

解决方案

我们设\(f[i][j]\)为聪聪在\(i\)点、可可在\(j\)点,聪聪抓到可可的期望时间

聪聪

首先,从题面里我们能够知道,聪聪每次走到的点是固定的,因此我们可以把它预处理出来。

\(step[i][j]\)表示当可可在j位置聪聪在i位置时聪聪的选择。我们可以直接枚举每个点,当成可可的位置进行BFS,然后就能求得\(step\)数组,具体过程参考代码进行理解(注意这里求解的是走一步的情况):

for(register int i=1;i<=n;i++) Get_step(i); //枚举可可的位置,求解step
inline void Get_step(int p) //step[i][j]表示当可可在j位置聪聪在i位置时聪聪的选择
{
	memset(vis,false,sizeof(vis));
	memset(deep,127,sizeof(deep));
	q.push(p);
	deep[p]=1,vis[p]=true;
	while(!q.empty()){
		int now=q.front(); q.pop();
		vis[now]=false;
		for(register int i=first[now];i;i=nex[i]){
			int to=v[i];
			if(!vis[to]&&deep[to]>deep[now]+1){
				deep[to]=deep[now]+1; //更新深度 
				step[to][p]=now; //记录目标点 
				q.push(to),vis[to]=true; //由于深度发生变化,重新入队 
			}
			else if(deep[to]==deep[now]+1) step[to][p]=min(step[to][p],now);
		}
	}
}

若是聪聪已经和可可在同一个位置,说明已经抓到,此时的期望为\(0\)。如果聪聪能够在两步内抓到可可,只需要经过\(1\)个单位时间,此时的期望为1

if(x==y) return 0.0;  //已经抓到,期望为0 
if(step[x][y]==y||step[step[x][y]][y]==y) return 1.0; //下一步即可捉到,期望为1

由于聪聪走一步的位置我们知道,那我他走两步的位置其实就是他走到下一步时再走一步,由step的定义我们易知为:\(step[step[x][y]][y]\)

可可

如果聪聪不能在两步内抓到可可,那么聪聪要多久才能抓住可可就和可可的选择有关了。

我们就要枚举可可走过的每种可能,和聪聪走到的位置一起进入下一状态,求得每个状态的权值。即为:

double sum=DFS(step[step[x][y]][y],y); //原地不动 
for(register int i=first[y];i;i=nex[i])
	sum+=DFS(step[step[x][y]][y],v[i]); //枚举选择

最后根据期望的定义我们知道他将会乘可可做出每种选择的概率,我们选择在最后除以选择的总数

我们可以在输入的时候统计每个点的出度,可可还可以原地不动,则出度加1即为选择的总数。

return f[x][y]=sum/(out[y]+1.0)+1.0; //可可可以不动,最后加1与扑克同理可证

最后看到这个式子,相信很多同学的疑惑在于,最后为什么会加上一个\(1\)。我们会发现,前面我们计算的都是每一个点的权值转移,但是我们并没有计算转移的代价。由于每转移一次是一个单位时间,我们将代价定为1,最后可可可能走的所有边的概率乘1再加起来,就等于\(1\)。而这个\(1\)是我们没有计算的,所以要在最后加上\(1\)

code

#include <bits/stdc++.h>
using namespace std;
const double eps=1e-8; 
const int N=1e3+10;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,st,ed;
double f[N][N];
int out[N],step[N][N];
int tot,v[2*N],nex[2*N],first[N];
inline void Add(int x,int y)
{
	nex[++tot]=first[x];
	first[x]=tot,v[tot]=y;
}
queue<int> q;
bool vis[N];
int deep[N];
inline double DFS(int x,int y)
{
	if(x==y) return 0.0;  //已经抓到,期望为0 
	if(step[x][y]==y||step[step[x][y]][y]==y) return 1.0; //下一步即可捉到,期望为1
	if(!(fabs(f[x][y])<eps)) return f[x][y]; //已经算过 
	double sum=DFS(step[step[x][y]][y],y); //原地不动 
	for(register int i=first[y];i;i=nex[i])
		sum+=DFS(step[step[x][y]][y],v[i]); //枚举选择 
	return f[x][y]=sum/(out[y]+1.0)+1.0; //可可可以不动,最后加1与扑克同理可证
}
inline void Get_step(int p) //step[i][j]表示当可可在j位置聪聪在i位置时聪聪的选择
{
	memset(vis,false,sizeof(vis));
	memset(deep,127,sizeof(deep));
	q.push(p);
	deep[p]=1,vis[p]=true;
	while(!q.empty()){
		int now=q.front(); q.pop();
		vis[now]=false;
		for(register int i=first[now];i;i=nex[i]){
			int to=v[i];
			if(!vis[to]&&deep[to]>deep[now]+1){
				deep[to]=deep[now]+1; //更新深度 
				step[to][p]=now; //记录目标点 
				q.push(to),vis[to]=true; //由于深度发生变化,重新入队 
			}
			else if(deep[to]==deep[now]+1) step[to][p]=min(step[to][p],now);
		}
	}
}
int main()
{
	n=read(),m=read(); st=read(),ed=read();
	for(register int i=1;i<=m;i++){
		int x=read(),y=read();
		Add(x,y),Add(y,x);
		out[x]++,out[y]++; //统计出度 
	}
	for(register int i=1;i<=n;i++) Get_step(i); //枚举可可的位置,求解step 
	printf("%.3lf\n",DFS(st,ed)); //记忆化搜索即可 
	return 0;
}
posted @ 2021-08-21 11:18  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(109)  评论(1编辑  收藏  举报