
题目描述
Alice和Bob在玩一个游戏。有n个石子在这里,Alice和Bob轮流投掷硬币,如果正面朝上,则从n个石子中取出一个石子,否则不做任何事。取到最后一颗石子的人胜利。Alice在投掷硬币时有p的概率投掷出他想投的一面,同样,Bob有q的概率投掷出他相投的一面。
现在Alice先手投掷硬币,假设他们都想赢得游戏,问你Alice胜利的概率为多少。
输入
第一行一个正整数t,表示数据组数。
对于每组数据,一行三个数n,p,q。
输出
对于每组数据输出一行一个实数,表示Alice胜利的概率,保留6位小数。
样例输入
1
1 0.5 0.5
样例输出
0.666667
提示
概率dp
这题真是巨坑。。。
f[i]表示i块石头先投者获胜的概率,g[i]表示i块石头后投者获胜的概率。
易推出:
$f[i]=\frac{p_0·g[i-1]+(1-p_0)·q_0·f[i-1]}{1-(1-p_0)·(1-q_0)}$
$g[i]=\frac{q_0·f[i-1]+(1-q_0)·p_0·g[i-1]}{1-(1-p_0)·(1-q_0)}$
然而这里$p_0$和$q_0$都是目标概率,而题目中的p和q都是几率,
所以需要根据情况决定是否想要正面朝上。
根据方程的推导:
A想让自己获胜的概率最大,即让$f[i]$最大。
假设$g[i-1]-f[i-1]$不等于$0$,把$f[i]$的推导式展开,得:
$f[i]=\frac{p_0·g[i-1]+(1-p_0)·q_0·f[i-1]}{1-(1-p_0)·(1-q_0)}\\\ \ \ \ \ \ =\frac{(p_0+q_0-p_0·q_0)·f[i-1]+p_0(g[i-1]-f[i-1])}{p_0+q_0-p_0·q_0}\\\ \ \ \ \ \ =f[i-1]+\frac{p_0(g[i-1]-f[i-1])}{p_0+q_0-p_0·q_0}\\\ \ \ \ \ \ =f[i-1]+\frac1{\frac{p_0+q_0-p_0·q_0}{p_0(g[i-1]-f[i-1])}}\\\ \ \ \ \ \ =f[i-1]+\frac1{\frac{1-q_0+\frac{q_0}{p_0}}{g[i-1]-f[i-1]}}$
显然当$g[i-1]-f[i-1]>0$时,$p_0$越大越好;当$g[i-1]-f[i-1]<0$时,$p_0$越小越好。
$q_0$的推导同理。
于是可以得到结论:
当f[i-1]<g[i-1]时,都想要正面朝上,$p_0=p$,$q_0=q$;
当f[i-1]>g[i-1]时,都不想要正面朝上,$p_0=1-p$,$q_0=1-q$。
但是n太大肿么办?
于是用到概率黑科技:
当n越来越大时,f[n]逐渐趋近于一个定值,而且题目中只要求保留6位小数。
所以就此题而言f[1000+k]可以近似等于f[1000]。
#include <cstdio>
#include <cstring>
double f[1001] , g[1001];
int main()
{
int t;
scanf("%d" , &t);
while(t -- )
{
int n , i;
double p , q;
scanf("%d%lf%lf" , &n , &p , &q);
memset(f , 0 , sizeof(f));
memset(g , 0 , sizeof(g));
if(n > 1000)
n = 1000;
f[0] = 0;
g[0] = 1;
for(i = 1 ; i <= n ; i ++ )
{
if(f[i - 1] > g[i - 1])
p = 1 - p , q = 1 - q;
f[i] = (p * g[i - 1] + (1 - p) * q * f[i - 1]) / (1 - (1 - p) * (1 - q));
g[i] = (q * f[i - 1] + (1 - q) * p * g[i - 1]) / (1 - (1 - p) * (1 - q));
if(f[i - 1] > g[i - 1])
p = 1 - p , q = 1 - q;
}
printf("%.6lf\n" , f[n]);
}
return 0;
}