题意:飞行棋,起点是 0 ,终点是 n 和 n 以后的点,然后会给出 m 条边,表示直接从 x 飞到 y,而且是必须飞,不需要扔骰子,然后就是飞行棋的规则,扔一次骰子,按照骰子的步数走多少步 ,求出从起点到终点的扔骰子的期望.
最近算是第一次做概率DP吧,学一学这个东西。
以 dp[i] 表示从 i 点到终点的期望次数,那么 dp[n] 到 dp[n+6] 就肯定是 0 了,这个就不用解释了.
由于求期望,逆推。那么对于 i 点,可以走到 i+1,i+2,一直到 i+6,概率都是 六分之一,那么就可以得出: dp[i] = (dp[i+1]+.....+dp[i+6])/6+1;
这个转移依靠的是期望的线性关系。这是在没有边的情况下的转移,但是由于有边,再来考虑有边的情况,对于边连接的两个点(x,y),有: dp[x] = dp[y] 这个性质,
因为在飞行棋中,x,y,是连接在一起的,其实可以看做是同一个点,那么对于这两个点的 dp 值,应该是相等的,这个要仔细推敲一下就能知道。
我的代码写得就比较复杂,用并查集维护了联通块,对于块内的点dp值肯定相同,其实可以写得很简单,我果然代码能力还是太弱。
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
int f[maxn],n,m,vis[maxn],flag[maxn],cnt[maxn];
double dp[maxn],p[maxn];
void inti()
{
memset(cnt,0,sizeof(cnt));
memset(flag,-1,sizeof(flag));
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++)
f[i]=i;
}
int findfa(int x)
{
if(x==f[x])
return x;
return f[x]=findfa(f[x]);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
inti();
if(n==0&&m==0)
break;
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
int dx=findfa(x);
int dy=findfa(y);
if(dx!=dy)
f[dx]=y;
}
for(int i=0;i<=n;i++)
{
int di=findfa(i);
vis[i]=di;
cnt[di]++;
}
for(int i=n;i<=n+7;i++)
dp[i]=0.0;
int di=findfa(n);
flag[di]=1;
p[di]=0.0;
for(int i=n-1;i>=0;i--)
{
int di=vis[i];
if(cnt[di]==1)
{
double tmp=0.0;
for(int j=1;j<=6;j++)
tmp+=dp[i+j];
dp[i]=tmp/6.0+1;
}
else
{
if(flag[di]==-1)
{
double tmp=0.0;
for(int j=1;j<=6;j++)
tmp+=dp[i+j];
dp[i]=tmp/6.0+1;
p[di]=dp[i];
flag[di]=1;
}
else
dp[i]=p[di];
}
}
printf("%.4f\n",dp[0]);
}
return 0;
}