HDU-4405(飞行棋步数期望的 正推和逆推)

https://vjudge.net/problem/HDU-4405
题意:格子编号 0 − n 0-n 0n,从起点 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=l6(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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值