P5249 [LnOI2019]加特林轮盘赌 题解

P5249 [LnOI2019]加特林轮盘赌

期望+高斯消元

因为是个环,不妨把这 \(n\) 只鹿想成排了一队,每次是队首拿加特林,他开一枪只有两种可能,要么在那 \(P_0\) 的概率里中枪了,要么在剩下的 \(1-P_0\) 的概率里没中枪,没中枪的话他就跑到队尾去当最后一只鹿了。

\(f_{i,j}\) 表示有 \(i\) 只鹿,第 \(j\) 只赢的概率。

就有了一个比较好想的状态转移方程:

\[f_{i,j}=P_0f_{i-1,j-1}+(i-P_0)f_{i,j-1} \ ,j\in[2,i] \]

其中 \(f_{i-1,j-1}\) 表示当前这只鹿死了,整体来看少了一只,这是有 \(P_0\) 的概率的;\(f_{i-1,j-1}\) 表示当前这只鹿没死,总数不变,这是有 \((1-P_0)\) 的概率的。

因为最后有且仅有一个幸存者,所以所有鹿做幸存者的概率和为 \(1\),得到:

\[\sum_{j=1}^if_{i,j}=1 \]

\(i\) 个方程找全了,看起来可以高斯消元了 ,但是 \(n\) 的范围是 \(10^4\),现在需要考虑优化,也就是手动高斯消元。

尝试把上面那个转移看成一个 \(y=kx+b\) 形式的一次函数,设 \(y=f_{i,j}\)\(x=f_{i,j-1}\),那么 \(k=(i-P_0)\),常数项就是 \(P_0f_{i-1,j-1}\)

发现这样设是可行的,因为 \(f_{i-1,j-1}\) 是在之前已经算出来的,而 \(f_{i,j-1}\) 可以由 \(f_{i,j-2}\) 得到,\(f_{i,j-2}\) 可以由 \(f_{i,j-3}\) 得到,\(f_{i,j-3}\) 可以由 \(f_{i,j-4}\) 得到……直到这一串由 \(f_{i,1}\) 得到。

所以 \(f_{i,j}\) 就是一个关于 \(f_{i,1}\) 的一次函数。求出这个函数,代入最后一个方程 \(a\cdot f_{i,1}+b=1\),就能求出 \(f_{i,1}\), 进而求出所有的。

注意特判一下 \(P_0=0\) 的情况。

\(code\)

#include<bits/stdc++.h>
#define N 1000010
#define int long long
#define debug cout<<"------------------"<<endl
using namespace std;

int n,k;
int now=1,lst=0;
double p0;
double fac[N],f[2][N];

signed main(){
    cin>>p0>>n>>k;
    if(p0==0) printf("%d\n",(n==1)?1:0),exit(0);
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=(1-p0)*fac[i-1];

    f[1][1]=1;
    for(int i=2;i<=n;i++){
        lst=now,now^=1;
        double sum=0;
        for(int j=1;j<=i-1;j++) sum+=fac[i-j]*f[lst][j];
        for(int j=1;j<=i;j++){
            f[now][j]=sum;
            sum=(1-p0)*(sum-fac[i-1]*f[lst][j])+f[lst][j];
        }
        sum=p0/(1-fac[i]);
        for(int j=1;j<=i;j++) f[now][j]*=sum;
    }

    printf("%.10f",f[now][k]);
    return 0;
}