- 有(n)个石子,两人轮流掷硬币,正面朝上则取一颗棋子,反面朝上则不操作,取到最后一颗石子的人获胜。
- 已知先手有(p)的概率掷出自己想要的面,后手有(q)的概率掷出自己想要的面,求先手获胜的概率。
- (n<10^8)
概率(DP)
容易想到分别设(f_i,g_i)表示轮到先手/后手操作且还剩(i)颗石子时,先手获胜的概率。
由于条件针对的是掷出自己想要的面,所以转移的时候我们首先要知道每个人会想要掷出哪一面。
然后发现这显然取决于(f_{i-1})和(g_{i-1}),若(g_{i-1}>f_{i-1})则二人都想要正面,否则二人都想要反面。
方便起见,设新的(p,q)分别表示先手/后手掷出正面的概率,列出转移方程:
[f_i=g_{i} imes(1-p)+g_{i-1} imes p\
g_i=f_i imes (1-q)+g_{i-1} imes q
]
由于转移关系成环,我们把(g_i)代入到(f_i)的式子中,于是得到:
[f_i=(f_i imes (1-q)+g_{i-1} imes q) imes(1-p)+g_{i-1} imes p\
f_i=frac{g_{i-1} imes q imes (1-p)+g_{i-1} imes p}{1-(1-q) imes (1-p)}
]
然后代入到(g_i)的式子中就可以求出(g_i)了。
(n)过大?
由于这道题是保留小数输出的,而发现当(n)过大,答案基本上保持不变,所以我们完全可以将(n)向一个较小的值(例如(10^3))取(min)。
实测我一开始把n取太大反而被卡精度了。
(O(T10^3))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define DB double
using namespace std;
int n;DB pp,qq,f[N+5],g[N+5];
int main()
{
RI Tt,i,j;DB p,q;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%lf%lf",&n,&pp,&qq),n=min(n,N),g[0]=i=1;i<=n;++i)//将n向1e3取min
g[i-1]>f[i-1]?(p=pp,q=qq):(p=1-pp,q=1-qq),//根据g[i-1]与f[i-1]的大小关系确定二人的最优策略
f[i]=(f[i-1]*q*(1-p)+g[i-1]*p)/(1-(1-q)*(1-p)),g[i]=f[i]*(1-q)+f[i-1]*q;//转移
printf("%.6lf
",f[n]);
}return 0;
}