把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;
}
posted @ 2021-05-24 14:08  TheLostWeak  阅读(41)  评论(0编辑  收藏  举报