[POJ3744]Scout YYF I 期望DP

说在前面

话说咱们一个初三的神犇的名字也是YYF(笑)
贴一份他的题解吧=w=yyf0309
这个题,是me第一次写矩阵快速幂,感觉写起来…..其实和普通快速幂几乎是一样的,只不过原来是数字相乘,现在是矩阵相乘而已。
而且还拿了个1A,开心!


题面

(某个)YYF在数轴上的1号点,他有p的概率向右跳一格,1-p的概率向右跳两格。数轴上分布有N个地雷,当(某个)YYF踩到地雷的时候,他就死了。问(某个)YYF安全的走过雷区的概率是多少

输入

多组数据,EOF结束
每组数据包含两行,第一行两个数字:整数N(≤10)和实数p(范围[0.25,0.75] )
第二行N个数,表示雷的位置(范围[1,100000000])

输出

每组数据输出一行一个实数,表示答案。保留七位小数。


题解

如果YYF遇到了一个地雷,并且还要能安全通过,那么只能是从地雷的前一格跳两步到地雷的后一格。
那么我们把整个数轴依据地雷所在位置划分成若干区域,如果YYF成功的越过了第i个雷,那么他下一步一定站在pos[i]+1上,到下一个雷被炸的概率就是从pos[i]+1跳到pos[i+1]的概率,用1减去这个概率得到的就是他安全通过的概率。

于是定义f[i]表示,到i位置YYF还活着的概率,明显有f[i] = p * f[i-1] + (1-p) * f[i-2]。
特别的,雷所在的位置的f值都为0【显然】


看似结束了的样子,然而数据规模特别大(1e8),还有多组数据,以至于我们直接递推会TLE。那么我们肯定需要对上述方法进行优化

解法一:矩阵快速幂
我们发现,这个递推式是形如f[i] = A * f[i-1] + B * f[i-2] + C * f[i-3] ….. 的形式,对于这类递推,我们都可以用矩阵乘法进行优化,如下所示:

[p11p0][ f[i1]  f[i2] ]=[ pf[i1]+(1p)f[i2] 1f[i1]]=[ f[i]  f[i1] ]

(这LaTex写的我心累= =)
好,我们发现,每计算一次,就相当于是乘上了一个矩阵
[p11p0]
那么对于每一小段之内的转移,我们就可以靠矩阵快速幂来辅助快速转移。
矩阵快速幂我就不讲了,原理和普通快速幂是一样的

解法二:dp收束
在liu_runda的博客园里,me见到了一种很新奇的解法。
就是说,因为每次我们转移都是f[i] = p * f[i-1] + (1-p) * f[i-2],并且p是固定的,因此这个dp存在收束性。可能转移了很多次之后,f会无限的逼近某一个值,我们只需要让这个”逼近”的误差在题目承受范围内就可以了。
liu_runda取的转移上限是500项,也就是说,大于如果一个雷离某个格子已经大于了500格,那么这个雷对那些格子的概率影响可以忽略不计,因此直接把远处的雷缩过来就好了
贴一个他的题解:liu_runda的POJ3744题解


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , pos[15] ;
double p , tg[2][2] , tp[2] ;

double M_pow( int b ){
    double gm[2][2] = { { p , 1-p },
                        { 1 , 0   } } ;
    double s[2] = { 1 , 0 } ;
    while( b ){
        if( b&1 ){
            tp[0] = gm[0][0] * s[0] + gm[0][1] * s[1] ;
            tp[1] = gm[1][0] * s[0] + gm[1][1] * s[1] ;
            s[0] = tp[0] ; s[1] = tp[1] ;
        }
        for( int i = 0 ; i <= 1 ; i ++ )
            for( int j = 0 ; j <= 1 ; j ++ )
                for( int k = 0 ; k <= 1 ; k ++ )
                    tg[i][j] += gm[i][k] * gm[k][j] ;
        for( int i = 0 ; i <= 1 ; i ++ )
            for( int j = 0 ; j <= 1 ; j ++ )
                gm[i][j] = tg[i][j] , tg[i][j] = 0 ;
        b >>= 1 ;
    }
    return s[0] ;
}

int main(){
    while( scanf( "%d%lf" , &N , &p ) != EOF ){
        for( int i = 1 ; i <= N ; i ++ )
            scanf( "%d" , &pos[i] ) ;
        sort( pos , pos + N + 1 ) ;
        double ans = 1 ;
        for( int i = 1 ; i <= N ; i ++ )
            ans *= ( 1 - M_pow( pos[i] - pos[i-1] - 1 ) ) ;
        printf( "%.7f\n" , ans ) ;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值