• KPGAME


    先考虑一下如果我想赢得游戏,我会采取的最优策略是什么。

    首先,想赢得游戏就是要取到最后一个石子,每次抛硬币相当于给你一次机会,每次机会都有相同概率取到石子,显然,最优策略就是让我最后一次取石子的机会越多。

    如何让机会最多?当然是取最后一个石子时,我先抛硬币,这样我的机会就越多。也就是说,我不想取倒数第二个石子。因为如果是我取了倒数第二个石子的话,取最后一个石子时先抛硬币的就是对手了。

    那么就可以考虑 dp 了:设 d p ( i , j = 0 / 1 , k = 0 / 1 ) dp(i,j=0/1,k=0/1) dp(i,j=0/1,k=0/1) 表示 Alice 和 Bob 都 想( k = 0 k=0 k=0)/不想( k = 1 k=1 k=1) 取到第 i i i 个石子,最后 Alice( j = 1 j=1 j=1)/Bob( j = 2 j=2 j=2) 实现愿望的概率。(实现愿望就是指“取到( k = 0 k=0 k=0)/没取到( k = 1 k=1 k=1) 第 i i i 个石子”)。

    接下来以 d p ( i , j , 0 ) dp(i,j,0) dp(i,j,0) 的转移为例:表示两个人都想要取到第 i i i 个石子,最后 j j j(代表 Alice 或 Bob)取到的概率。

    显然,两个人的最优策略都是他们都想先抛第 i i i 个石子的硬币。意思就是说他们都不想取第 i − 1 i-1 i1 个石子。

    1. 假设 j j j 成功地没取到第 i − 1 i-1 i1 个石子,那么有:(其中设 p [ 0 ] = p p[0]=p p[0]=p p [ 1 ] = q p[1]=q p[1]=q

      d p ( i , j , 0 ) = d p ( i − 1 , j , 1 ) × p j × { 1 + ( 1 − p j ) ( 1 − p j ⊕ 1 ) ⏟ 第一轮两人都没扔到正面的概率 + [ ( 1 − p j ) ( 1 − p j ⊕ 1 ) ] 2 ⏟ 前两轮两人都没扔到正面的概率 + [ ( 1 − p j ) ( 1 − p j ⊕ 1 ) ] 3 ⏟ 前三轮两人都没扔到正面的概率 + …   } egin{aligned}dp(i,j,0)=&dp(i-1,j,1) imes p_j imes\&{1+underbrace{(1-p_j)(1-p_{joplus1})}_{ ext{第一轮两人都没扔到正面的概率}}+underbrace{[(1-p_j)(1-p_{joplus1})]^2}_{ ext{前两轮两人都没扔到正面的概率}}+underbrace{[(1-p_j)(1-p_{joplus1})]^3}_{ ext{前三轮两人都没扔到正面的概率}}+dots}end{aligned} dp(i,j,0)=dp(i1,j,1)×pj×{1+第一轮两人都没扔到正面的概率 (1pj)(1pj1)+前两轮两人都没扔到正面的概率 [(1pj)(1pj1)]2+前三轮两人都没扔到正面的概率 [(1pj)(1pj1)]3+}

      最后面那个括号里的东西有点复杂,考虑化简:

      t 0 = ( 1 − p j ) ( 1 − p j ⊕ 1 ) t_0=(1-p_j)(1-p_{joplus1}) t0=(1pj)(1pj1),令

      S 0 = lim ⁡ k → ∞ 1 + t 0 + t 0 2 + ⋯ + t 0 k S 0 t 0 = lim ⁡ k → ∞ t 0 + t 0 2 + ⋯ + t 0 k + 1 egin{aligned}S_0&=lim_{k ightarrow infty}1+t_0+t_0^2+dots+t_0^k\S_0t_0&=lim_{k ightarrow infty}t_0+t_0^2+dots+t_0^{k+1}end{aligned} S0S0t0=klim1+t0+t02++t0k=klimt0+t02++t0k+1

      上式减下式得:

      S 0 ( 1 − t 0 ) = lim ⁡ k → ∞ 1 − t 0 k + 1 S 0 = lim ⁡ k → ∞ 1 − t 0 k + 1 1 − t 0 = 1 − 0 1 − t 0 ( 0 < t 0 < 1  得 lim ⁡ k → ∞ t 0 k + 1 = 0 ) = 1 1 − t 0 egin{aligned}S_0(1-t_0)&=lim_{k ightarrow infty}1-t_0^{k+1}\S_0&=frac{lim_{k ightarrow infty}1-t_0^{k+1}}{1-t_0}\&=frac{1-0}{1-t_0}(0<t_0<1 ext{ 得}lim_{k ightarrow infty}t_0^{k+1}=0)\&=frac{1}{1-t_0}end{aligned} S0(1t0)S0=klim1t0k+1=1t0limk1t0k+1=1t010(0<t0<1 klimt0k+1=0)=1t01

      那么就有: d p ( i , j , 0 ) = d p ( i − 1 , j , 1 ) × p j × S 0 dp(i,j,0)=dp(i-1,j,1) imes p_j imes S_0 dp(i,j,0)=dp(i1,j,1)×pj×S0

    2. 假设 j j j 没能成功地不取第 i − 1 i-1 i1 个石子,也就是说他不幸地取到了第 i − 1 i-1 i1 个石子,那么必须得先让对手取一次,再轮流取,即:

      d p ( i , j , 0 ) = [ 1 − d p ( i − 1 , j , 1 ) ] ⏟ 上次没成功的概率 × ( 1 − p j ⊕ 1 ) ⏟ 对手第一次抛硬币没扔到正面的概率 × p j × S 0 ⏟ 接下来轮流抛, j  抛到正面的概率 dp(i,j,0)=underbrace{[1-dp(i-1,j,1)]}_{ ext{上次没成功的概率}} imes underbrace{(1-p_{joplus1})}_{ ext{对手第一次抛硬币没扔到正面的概率}} imesunderbrace{p_j imes S_0}_{ ext{接下来轮流抛,$j$ 抛到正面的概率}} dp(i,j,0)=上次没成功的概率 [1dp(i1,j,1)]×对手第一次抛硬币没扔到正面的概率 (1pj1)×接下来轮流抛,j 抛到正面的概率 pj×S0

    综述,可以推出:

    d p ( i , j , 0 ) = d p ( i − 1 , j , 1 ) × p j × S 0 + [ 1 − d p ( i − 1 , j , 1 ) ] × ( 1 − p j ⊕ 1 ) × p j × S 0 dp(i,j,0)=dp(i-1,j,1) imes p_j imes S_0+[1-dp(i-1,j,1)] imes(1-p_{joplus1}) imes p_j imes S_0 dp(i,j,0)=dp(i1,j,1)×pj×S0+[1dp(i1,j,1)]×(1pj1)×pj×S0

    自己按上面的方法推一下,同理可得:

    d p ( i , j , 1 ) = d p ( i − 1 , j , 0 ) × ( 1 − p j ⊕ 1 ) × S 1 + [ 1 − d p ( i − 1 , j , 0 ) ] × p j × ( 1 − p j ⊕ 1 ) × S 1 dp(i,j,1)=dp(i-1,j,0) imes (1-p_{joplus 1}) imes S_1+[1-dp(i-1,j,0)] imes p_j imes (1-p_{joplus 1}) imes S_1 dp(i,j,1)=dp(i1,j,0)×(1pj1)×S1+[1dp(i1,j,0)]×pj×(1pj1)×S1

    (其中 S 1 = 1 1 − t 1 S_1=frac{1}{1-t_1} S1=1t11 t 1 = p j × p j ⊕ 1 t_1=p_j imes p_{joplus1} t1=pj×pj1

    按这种方法 dp(要滚动一下),发现时间复杂度是 O ( n ) O(n) O(n) 的,过不去。

    猜测用矩阵快速幂加速或者有收敛,手测了几组大数据之后发现有收敛,于是就过了。(当然用矩阵快速幂应该也可以)

    代码如下:

    #include<bits/stdc++.h>
    
    using namespace std;
    
    int t,n;
    double p[2],dp[2][2][2];
    
    int main()
    {
    	scanf("%d",&t);
    	while(t--)
    	{
    		scanf("%d%lf%lf",&n,&p[0],&p[1]);
    		n=min(n,1000);//收敛
    		double s[2];
    		s[0]=(1-(1-p[0])*(1-p[1]));
    		s[1]=(1-p[0]*p[1]);
    		memset(dp,0,sizeof(dp));
    		dp[0][0][1]=dp[0][1][0]=1;
    		int now=1;//滚动数组(其实收敛之后不用滚动都可以)
    		for(int i=1;i<=n;i++,now^=1)
    		{
    			for(int j=0;j<2;j++)
    			{
    				dp[now][j][0]=dp[now^1][j][1]*p[j]/s[0]+(1-dp[now^1][j][1])*(1-p[j^1])*p[j]/s[0];
    				dp[now][j][1]=dp[now^1][j][0]*(1-p[j^1])/s[1]+(1-dp[now^1][j][0])*p[j]*(1-p[j^1])/s[1];
    			}
    		}
    		printf("%lf
    ",dp[now^1][0][0]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    二叉树的建树,按层遍历,结点总数,页结点,深度以及三序非递归遍历二叉树,建立中序线索二叉树
    志愿者选拔(单调队列)
    重建二叉树(中后序求前序)
    New Year Table(几何)
    Hopscotch(细节)
    红黑树(中序二叉树)
    Linux-awk命令详解
    Python--常用模块
    Python--re模块
    Python--模块与包
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448663.html
Copyright © 2020-2023  润新知