说在前面
话说咱们一个初三的神犇的名字也是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] ….. 的形式,对于这类递推,我们都可以用矩阵乘法进行优化,如下所示:
(这LaTex写的我心累= =)
好,我们发现,每计算一次,就相当于是乘上了一个矩阵
矩阵快速幂我就不讲了,原理和普通快速幂是一样的
解法二: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 ) ;
}
}