[CodeForces 98E] Help Shrek and Donkey

题目大意

有两个人在玩一个游戏,规则如下:

  1. 每个人的手上有一些牌,桌子上有一张牌,每个人只能看到自己手上的牌。(桌子上的看不到)

  2. 每张牌都不一样。

  3. 两个人轮流进行如下两个操作之一:

    • 猜桌子上的那张牌是什么,若猜对了,即获得胜利,否则算输掉比赛。

    • 询问对方手中有没有一张牌,对方必须如实回答。

现在假设两个人都会执行最优策略,给定先后手的牌数$n,m$,询问两个人的胜率。

$n,m \leqslant 1000$

分析

不难发现,一个人若是进行询问操作,则有两种问法:

  1. 询问自己手上没有的牌。(我们记这种询问为诚实的询问)

  2. 询问自己手上有的牌。(我们记这种询问为欺骗的询问)

因为一个比较显然的思路是每次询问是问自己手上没有的牌(这样才能获得更多的信息)

然而我没也可以询问自己手上有的牌来误导对方自己手上没有这张牌,然后对方也没有这张牌的话就会认为桌子上是这张牌并猜是这张牌,然后他就输了。

然后对方也可以猜我这次的询问是不是诚实的询问(我们记为是否相信)。

题解

我们设$f(n,m)$为先手有$n$张牌,后手有$m$张牌时先手的胜率。

从刚才的分析中我们可以根据询问的诚实与否和是否相信来分为4种情况。

情况的具体分析

1. 先手进行诚实询问,后手相信这次询问。

那么会有以下两种情况:

  1. $\frac{1}{m+1}$的概率猜到桌子上的牌,那么后手获胜(先手已经进行了操作,而后手已经知道了问的那张牌自己没有同时对方也没有,也就是桌子上的牌),对$f(n,m)$的贡献为$0$。
  2. $\frac{m}{m+1}$的概率猜到后手手上的牌,那么先手知道了后手有问的那张牌(相当于后手失去了一张牌),后手知道了先手没有问的那张牌(没有得到新的信息),对$f(n,m)$的贡献是$\frac{m}{m+1} \times (1-f(m-1,n))$。(因为接下来先后手互换)

2.先手进行诚实询问,后手不相信这次询问。

会出现以下两种情况:

  1. $\frac{1}{m+1}$的概率猜到桌子上的牌,那么后手会认为这张桌子上的牌会在先手的手中,也就是后手必败,对$f(n,m)$的贡献是$\frac{1}{m+1}$。
  2. $\frac{m}{m+1}$的概率猜到后手手上的牌,那么后手肯定会相信这次询问,那么对答案的贡献就和上面一样了,即为$\frac{m}{m+1} \times (1-f(m-1,n))$。

3.先手进行欺骗询问,后手相信这次询问。

那么后手就中招了,自己手上没有那张牌,且认为先手手上也没有问的那张牌,那么就会认为那张牌就是桌子上的牌,这样先手就会必胜,对$f(n,m)$的贡献是$1$。

4.先手进行欺骗询问,后手不相信这次询问。

那么后手就会多知道先手手的一张牌(相当于先手失去了一张牌),对$f(n,m)$的贡献是$1-f(m,n-1)$。

做法

不难发现,这是一种混合策略的博弈游戏,就需要纳什均衡的那一套理论。

我们不妨假设先手进行诚实询问的概率为$p$,则进行欺骗询问的概率就是$1-p$。

那么后手选相信时对答案的贡献就是$p \times \frac{m}{m+1} \times (1-f(m-1,n)) + (1-p) \times 1$。

后手选不相信时对答案的贡献就是$p \times (\frac{1}{m+1}+\frac{m}{m+1} \times (1-f(m-1,n))) +(1-p) \times 1-f(m,n-1)$。

根据纳什均衡的理论,先手的最优决策点就是纳什均衡点,就是无论后手如何选择,答案均不会比当前情况更差的那个$p$,在这里就是令上下式子相等的那个$p$,解下方程就行了。

最后我们再记忆化一下就可以做到$\mathcal{O}(nm)$了。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>

using namespace std;

const int MAXN=1010;

bool vis[MAXN][MAXN];
long double f[MAXN][MAXN];

long double F(int n,int m)
{
if(vis[n][m]) return f[n][m];
vis[n][m]=1;
if(!n) return f[n][m]=1.0/(m+1);
if(!m) return f[n][m]=1;
long double k1=1.0*m/(m+1)*(1-F(m-1,n))-1,k2=1.0*m/(m+1)*(1-F(m-1,n))+1.0/(m+1)+F(m,n-1)-1;
long double x=(-F(m,n-1))/(k1-k2);
return f[n][m]=x*(1.0*m/(m+1)*(1-F(m-1,n)))+1-x;
}

int main()
{
int n,m;
cin>>n>>m;
long double Ans=F(n,m);
cout<<fixed<<setprecision(15)<<Ans<<" "<<1-Ans<<endl;
}