2318: Spoj4060 game with probability Problem
Description
Alice和Bob在玩一个游戏。有n个石子在这里,Alice和Bob轮流投掷硬币,如果正面朝上,则从n个石子中取出一个石子,否则不做任何事。取到最后一颗石子的人胜利。Alice在投掷硬币时有p的概率投掷出他想投的一面,同样,Bob有q的概率投掷出他相投的一面。
现在Alice先手投掷硬币,假设他们都想赢得游戏,问你Alice胜利的概率为多少。
Input
第一行一个正整数t,表示数据组数。
对于每组数据,一行三个数n,p,q。
Output
对于每组数据输出一行一个实数,表示Alice胜利的概率,保留6位小数。
Sample Input
1
1 0.5 0.5
Sample Output
0.666667
HINT
概率dp的部分不难写,博弈论的策略没想出来。
设$f[i]$表示有$i$枚石子,先手获胜的概率;
$g[i]$表示有$i$枚石子,后手获胜的概率;
则当有$i+1$枚石子时:
若$f[i]>g[i]$,则$Alice$希望在有$i$枚石子时取得先手,那么她希望这轮不取;
否则,$Alice$希望这轮取得石子;
当Alice希望取得石子时:
若Alice在本轮中先手,则有$p$的概率在下轮取得后手,有$1-p$的概率在本轮取得后手;
若Alice在本轮中后手,则有$q$的概率在下轮取得先手,有$1-q$的概率在本轮取得先手;
即$$egin{cases}f_i=p*g_{i-1}+(1-p)*g_i\g_i=q*f_{i-1}+(1-q)*f_iend{cases}$$
化简得$$f_i=frac{p*g_{i-1}+(1-p)*q*f_{i-1}}{1-(1-p)*(1-q)}$$
$$g_i=frac{q*f_{i-1}+(1-q)*p*g_{i-1}}{1-(1-p)*(1-q)}$$
当Alice不希望取得石子时:$p$和$(1-p)$ $q$和$(1-q)$取反即可
倒推dp即可。
有两个注意的点:
n很大,数组肯定是装不下的,显然要用滚动数组。
O(n)的时间复杂度显然是跑不过的,打表可以发现答案是收敛的,n很大的时候前6位小数已经固定了,n取min(n,100)就好。似乎是个常见的套路,还是too young,见得不够多。
1 #include<cstdio> 2 #include<algorithm> 3 #define foru(i,x,y) for(int i=x;i<=y;i++) 4 using namespace std; 5 int T,n,fl; 6 double f[2],g[2],p,q,p_,q_; 7 int main(){ 8 scanf("%d",&T); 9 while(T--){ 10 scanf("%d%lf%lf",&n,&p,&q); 11 n=min(n,100); 12 f[0]=0;g[0]=1;q_=1-q;p_=1-p; 13 int i=1; 14 foru(j,1,n){ 15 if(f[i^1]>g[i^1])swap(p_,p),swap(q_,q),fl=1; 16 f[i]=(p*g[i^1]+p_*q*f[i^1])/(1-q_*p_); 17 g[i]=(q*f[i^1]+q_*p*g[i^1])/(1-q_*p_); 18 if(fl==1)swap(p_,p),swap(q_,q),fl=0;i^=1; 19 } 20 printf("%.6lf ",f[i^1]); 21 } 22 }