【SPOJ KPGAME】A game with probability(概率DP+卡精度)
- 有\(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}\times(1-p)+g_{i-1}\times p\\
g_i=f_i\times (1-q)+g_{i-1}\times q
\]
由于转移关系成环,我们把\(g_i\)代入到\(f_i\)的式子中,于是得到:
\[f_i=(f_i\times (1-q)+g_{i-1}\times q)\times(1-p)+g_{i-1}\times p\\
f_i=\frac{g_{i-1}\times q\times (1-p)+g_{i-1}\times p}{1-(1-q)\times (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\n",f[n]);
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒