https://vjudge.net/problem/HDU-4405
题意:格子编号
0
−
n
0-n
0−n,从起点
0
0
0出发,扔骰子前进,当当前步数加上骰子上的数字
≥
n
≥n
≥n时游戏结束。同时有些格点直接相连,即若a,b相连,当落到a点时直接飞向b点,求至游戏结束时的步数期望。
先说逆推,我们可以用step[i]表示从i到n的期望步数,初始化step[n]=0。因为下一步的去向以及去向的概率很容易确定,所以:①如果直接相连,那么有step[a]=step[b];②否则,它可以先到达后面6格子中的一个,再到n,因为到后面每个格子的概率都是1/6,说以step[i] = ∑ k = l 6 =\sum\limits _{k=l}^6 =k=l∑6(step[i-k]+1) ∗ 1 6 *\frac{1}{6} ∗61
//倒推//上一个状态转移得(填表)
#include<cstdio>
const int manx=1e5+10;
int n,m,net[manx];
double step[manx];//每个格子到n的步数的期望值
int main()
{
int s,e;
while(scanf("%d%d",&n,&m),n||m)
{
for(int i=0;i<=n+6;i++)net[i]=step[i]=0;
while(m--)
{
scanf("%d%d",&s,&e);
net[s]=e;
}
for(int i=n-1;i>=0;i--)
{
if(net[i])step[i]=step[net[i]];
else step[i]=(step[i+1]+step[i+2]+step[i+3]+step[i+4]+step[i+5]+step[i+6])/6+1;
}
printf("%.4f\n",step[0]);
}
return 0;
}
关于正推:
首先,步数的期望 = ∑ ( =\sum( =∑(每一个可能的步数 × × ×以这个步数到达的概率 ) ) )
同样,我们可以用step[i]表示0到i的期望步数(即当我们到达i时 的期望步数),初始化step[0]=0。然后我们可以通过扔骰子从前面的几个格子到达i,或者从某个格子坐小飞机直接飞过来,但是每一种来源的概率不清楚。
所以我们用另一个数组p[i]来表示 能够到达i的概率(很明显p[0]=p[n]=1),初始化p[0]=1。现在考虑当我们达到i时,以每一种方式过来的概率:
①从点x坐小飞机直接飞到i:因为到达x的概率是p[x],且x只能飞向i,所以从点x过来的概率是p[x]/p[i];②从点i-k扔骰子过来,因为到达i-k的概率是p[i-k],且点i-k到i的概率是1/6,所以从点i-k过来的概率是(p[i-k]
∗
1
6
*\frac{1}{6}
∗61)/p[i]
//顺推//从上一个状态转移得(填表)
#include<cstdio>
const int manx=1e5+10;
//顺推来源的概率不一样。而逆推去向的概率一样。
int n,m,net[manx];
double step[manx];//0到每个格子的步数的期望值
double p[manx];//0能到达某个格子的概率
int main()
{
int s,e;
while(scanf("%d%d",&n,&m),n||m)
{
for(int i=0;i<=n;i++)p[i]=net[i]=step[i]=0;
while(m--)
{
scanf("%d%d",&s,&e);
net[s]=e;
}
p[0]=1;
for(int i=0;i<=n;i++)
{
//处理期望步数
for(int j=max(0,i-6);j<=i-1;j++)
{
double f=1;
if(i==n)f=7-n+j;//f是因为下一个格子大于等于n是都算游戏结束
step[i]+=f*(step[j]+1)*p[j]/6;
}
if(p[i])
step[i]/=p[i];
//处理概率,同时处理可以做小飞机的地方
if(net[i])
{
p[net[i]]+=p[i];
step[net[i]]+=p[i]*step[i];
p[i]=0;//这里赋为0后面就不用判断是否能从这一点摇子过去了
}
else
{
for(int j=i+1;j<=i+6;j++)
{
int temp=min(n,j);
p[temp]+=p[i]/6;
}
}
}
printf("%.4f\n",step[n]);
}
return 0;
}
/*
10 6
2 5
3 5
4 5
5 7
7 9
6 9
*/
但是因为没注意除p[i]的时候p[i]可能等于0,百度也没有找到正推的写法,然后过几天写次过几天写次还是一直没发现问题。
另外这里有没有相连点时顺推和逆推的写法:用投色子问题分析为什么顺着推、期望反着推 / 概率论
wa_2021.1.18:可能存在 有的点不能到达 的情况
关于正推和逆推的讨论:https://www.luogu.com.cn/discuss/show/133108
//顺推//转移向下一个状态(刷表)
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
using namespace std;
#define ll long long
//#define int long long
#define ull unsigned long long
#define PII pair<int,int>
#define mid ((l + r)>>1)
#define chl (root<<1)
#define chr (root<<1|1)
#define lowbit(x) ( x&(-x) )
const int manx = 1e5 + 10;
const int manx2 = 4e7 + 10;
const int INF = 2e9;
const int mod = 1e4+7;
int n,m,net[manx],s,t;
double step[manx],p[manx];
int main()
{
while(scanf("%d%d",&n,&m),n||m){
for(int i=0;i<=n;i++)p[i]=step[i]=net[i]=0;
for(int i=1;i<=m;i++){
scanf("%d%d",&s,&t);
net[s]=t;
}
p[0]=1;
for(int i=0;i<n;i++){
if(p[i])step[i]/=p[i];//p[i]可能为0
if(net[i]){
step[net[i]]+=step[i]*p[i];
p[net[i]]+=p[i];
}
else{
for(int j=1;j<=6;j++){
int nx=min(n,i+j);
step[nx]+=(step[i]+1)*p[i]/6;
p[nx]+=p[i]/6;
}
}
}
printf("%.4f\n",step[n]);
}
return 0;
}