[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;
}