【bzoj2318】Spoj4060 game with probability Problem 概率dp

题目描述

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

提示

数据范围:
1<=t<=50
0.5<=p,q<=0.99999999
对于100%的数据 1<=n<=99999999
题解

概率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]。

于是时间复杂度就降为O(1000T),可解。
#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;
}
posted @ 2016-12-20 18:55  GXZlegend  阅读(538)  评论(2编辑  收藏  举报