LOJ 1265-Snakes and Ladders(期望DP+高斯消元)

LOJ 1265-Snakes and Ladders(期望DP+高斯消元)

题目链接:[传送门](LightOJ - 1151)

其实这道题并不难,写下这篇博客的目的是纪念这种做法,当期望DP方程的递推关系拓扑图有环时,我们可以用高斯消元的方法去做(因为每一个位置都可以列一个方程,且都线性无关,所以可以用高斯消元解方程)

题意:

100 100 100个方格,其中从左到右编号依此为 1 1 1 100 100 100,现在我们有一个6个面的骰子,我们每次掷骰子,得到多少点数当前就会跳几个格子,比如我们现在在 1 1 1号格子,点数为 5 5 5,那么我们就会直接跳到 6 6 6号点。

除了掷骰子外,格子上有 m m m 个传送关系,表示为 ( a , b ) (a,b) (a,b),且 a ≠ b a\ne b a=b,传送关系的作用是,如果你到了 a a a 号点,那么你必须会被传送到 b b b 号点。

现在我们的初始位置是 1 1 1 ,且给出 m m m个传送关系,问到达位置 100 100 100的掷骰子的期望次数是多少?需要注意的是,如果掷骰子后将要跳跃的位置超过 100 100 100时,需要重新再掷骰子,直到将要跳跃的位置不超过 100 100 100时停止。

思路:

众所周知,期望DP倒着求(容易求)。

我们用 d p [ i ] dp[i] dp[i]表示 i i i 号格子到终点掷骰子的期望次数。

  • 如果 i i i 号点有传送且传送到 t p [ i ] tp[i] tp[i] d p [ i ] = d p [ t p [ i ] ] dp[i]=dp[tp[i]] dp[i]=dp[tp[i]]

  • 否则:令 k = ( 100 − i , 6 ) k=(100-i,6) k=(100i,6) d p [ i ] = 1 6 ∗ ∑ j = 1 k d p [ i + j ] + 6 − k 6 ∗ d p [ i ] + 1 dp[i]=\frac{1}{6}*\sum_{j=1} ^{k}dp[i+j]+\frac{6-k}{6}*dp[i]+1 dp[i]=61j=1kdp[i+j]+66kdp[i]+1

    然后化简后得出 d p [ i ] dp[i] dp[i]的表达式即可。

因为 t p [ i ] tp[i] tp[i] 可能小于 i i i ,所以我们不能单纯的从后往前 d p dp dp。我们考虑将 d p [ i ] dp[i] dp[i]设为第 i i i 个未知数,则可以建立100个有100个未知数的一次方程,然后高斯消元进行求解即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const double eps=1e-7;
double g[105][105],ans[105];
bool Gauss(int n)
{
    //A*x=B
    for(int i=1;i<=n;++i)
    {
        int r=i;
        for(int j=i+1;j<=n;++j) if(fabs(g[r][i])<fabs(g[r][j])) r=i;
        if(fabs(g[r][i]) < eps) return false;
        swap(g[i],g[r]);
        double div=g[r][i];
        for(int j=i;j<=n+1;++j) g[i][j]/=div;
        for(int j=i+1;j<=n;++j)
        {
            div=g[j][i];
            for(int p=i;p<=n+1;++p) g[j][p]-=div*g[i][p];
        }
    }
    ans[n]=g[n][n+1];
    for(int i=n-1;i>=1;--i)
    {
        ans[i]=g[i][n+1];
        for(int j=i+1;j<=n;++j) ans[i]-=g[i][j]*ans[j];
    }
    return true;
}
int to[105];//传送关系
int main()
{
    int t,cas=0;
    scanf("%d",&t);
    while(t--)
    {
        int m,n=100;
        scanf("%d",&m);
        fill(to+1,to+n+1,0);
        for(int i=1;i<=m;++i)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            to[a]=b;//a可以传送到to[a]
        }
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n+1;++j) g[i][j]=0.0;//初始化高斯消元矩阵
        g[n][n]=1.0;//终点的方程
        g[n][n+1]=0.0;
        for(int i=1;i < n;++i)
        {
            if(to[i])
            {
                g[i][i]=1;
                g[i][to[i]]=-1;
                g[i][n+1]=0.0;
            }
            else
            {
                int k=min(6,100-i);
                g[i][i]=1;
                for(int j=1;j<=k;++j)
                    g[i][i+j]=-1.0/k;
                g[i][n+1]=6.0/k;
            }
        }
        Gauss(n);
        printf("Case %d: %.9f\n",++cas,ans[1]);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值