【CF98E】Help Shrek and Donkey(博弈,纳什均衡)

题面

有一副互不相同的牌共 n + m + 1 n+m+1 n+m+1 张,编号 1 ∼ n + m + 1 1\sim n+m+1 1n+m+1。有两个人,第一个人 n n n 张,第二个人 m m m 张。另外有一张牌放在桌子上。

两个人玩游戏轮流操作(其中第一个人为先手)。有如下 2 2 2 中操作类型:

  • 猜测:猜桌上的那张牌是什么。如果猜对了则获胜,猜错了则失败。操作完之后游戏结束。

  • 指定:报一张牌的名字,如果对方手上有这张牌,则将该牌丢弃,游戏继续;如果对方手上没有这张牌,对方则会表示他不用由此牌。

现在假设两个人都知道自己的牌分别是什么,但是不知道桌上和对方手里的牌具体是什么。

若双方都采取最优策略进行游戏,问先手和后手获胜概率。

若选手答案与标准答案的误差不超过 1 0 − 9 10^{-9} 109 则认为正确。

数据范围: 0 ≤ n , m ≤ 1000 0\le n,m\le 1000 0n,m1000

题解

虽然是采取最优策略进行的,但是往往没有必胜策略。这里所说的最优策略是指,让获胜概率最大化。

不难发现,当角色丢弃一张牌后,又变成了一个总牌数更少的、先后手交换的起始局面,因此可以设 f ( n , m ) f(n,m) f(n,m) 为输入 n , m n,m n,m 时先手的获胜概率。


我们先解决边界情况: n = 0 n=0 n=0 m = 0 m=0 m=0

若对方的牌数为 0,那么可以直接推出桌上那张牌的编号,下一步便会直接「猜测」,然后 100% 取胜。为了避免这种情况,对方绝对不能给下一步进行的机会。所以,整个游戏的流程就是:先手「猜测」、判断输赢。

赢的概率是 m + 1 m+1 m+1 张牌中随机一张选对的概率, f ( n , m ) = 1 m + 1 f(n,m)=\frac{1}{m+1} f(n,m)=m+11


然后,我们考虑都有牌的情况。

如果当前状态等价于一个初始状态的话,没有人会直接猜测的,证明详见:笑长(zhang3)博客 思路第 11 行开始。

不(挺)难发现,「指定」有两种方式:

  • 「交流」:询问一张自己没有的牌。如果对方有,对方会将其丢弃,然后你知道在场不可能再有那张牌。或者对方没有,说明就是桌面上那张牌,但是下一步不是你的……
  • 「欺骗」:询问一张自己有的牌。你知道这是你的牌,对方不可能有,也不可能是桌上,所以会得到“没有牌”的指定结果。如果对方信以为真,下一步真去「猜测」了,那么你就赢了。这个操作的存在使得在「交流」中,即使得到了“没有牌”的结果,对方也没有100%的底气「猜测」。
    另一方面,如果对方不信,你也不可能去「猜测」,对方便会知道你在「欺骗」,进而知道你有这张牌。大家都知道了,那么这张牌就等于被丢弃了——被你这个先手丢弃了。从对方「不信」开始,这一结果就被笃定。

于是,围绕着先手「交流」或「欺骗」,后手「信任」(意味着下一步「猜测」)或「不信」,就有这样的状态转移表:
后 手   先 手 「 交 流 」 「 欺 骗 」 ( 丢 弃 ) m m + 1 ( 1 − f ( m − 1 , n ) ) − 「 信 任 」 0 1 「 不 信 」 1 m + 1 1 − f ( m , n − 1 ) \begin{matrix} _{后手}~^{先手}&「交流」&「欺骗」\\ (丢弃) & \frac{m}{m+1}(1-f(m-1,n)) & -\\ 「信任」& 0 & 1\\ 「不信」& \frac{1}{m+1} & 1-f(m,n-1) \end{matrix}  ()m+1m(1f(m1,n))0m+1111f(m,n1)

由于指定与猜测结果的随机性很大,所以决策上,先后手没有必胜策略,也会选择以一定概率进行「交流」/「欺骗」,或「信任」/「不信」。我们设先手选择「交流」的概率为 p p p ,后手选择「信任」的概率为 q q q

那么可以得出这个大式子:
f ( n , m ) p , q = p ( q + ( 1 − q ) ( 1 − f ( m , n − 1 ) ) ) + ( 1 − p ) ( − q m + 1 + 1 m + 1 + m m + 1 ( 1 − f ( m − 1 , n ) ) ) f(n,m)_{p,q}=p\Big(q+(1-q)\big(1-f(m,n-1)\big)\Big)\\ +(1-p)\left( -\frac{q}{m+1}+\frac{1}{m+1}+\frac{m}{m+1}(1-f(m-1,n)) \right) f(n,m)p,q=p(q+(1q)(1f(m,n1)))+(1p)(m+1q+m+11+m+1m(1f(m1,n)))

在博弈中,双方对于 p p p q q q 的选取都是透明的,对于确定的 p p p ,后手会调整 q q q 使得答案最小,对于确定的 q q q ,先手也会调整 p p p 使得答案最大,所以,答案最终就会变为
min ⁡ q ∈ [ 0 , 1 ] ( max ⁡ p ∈ [ 0 , 1 ] f ( n , m ) p , q ) \min_{q\in[0,1]}\left( \max_{p\in[0,1]} f(n,m)_{p,q} \right) q[0,1]min(p[0,1]maxf(n,m)p,q)

假使我们知道了 q q q ,那么不难发现式子就会变为关于 p p p 的一次式, p p p 一定会选取 0 , 1 0,1 0,1 其中一个了。当 p p p 分别取 0 , 1 0,1 0,1 的时候,就是两个关于 q q q 的一次式(刚好分别是上述 f ( n , m ) p , q f(n,m)_{p,q} f(n,m)p,q 式子的第二行和第一行), q q q 不论取什么值,最终答案都将是这两个一次式中较大的那个。还是把两个式子写出来吧:
p = 0 : f = − q m + 1 + 1 m + 1 + m m + 1 ( 1 − f ( m − 1 , n ) )   p = 1 : f = q + ( 1 − q ) ( 1 − f ( m , n − 1 ) )                               p=0:f=-\frac{q}{m+1}+\frac{1}{m+1}+\frac{m}{m+1}(1-f(m-1,n))\\ ~\\ p=1:f=q+(1-q)\big(1-f(m,n-1)\big)~~~~~~~~~~~~~~~~~~~~~~~~~~~\, p=0:f=m+1q+m+11+m+1m(1f(m1,n)) p=1:f=q+(1q)(1f(m,n1))                           

那么,就相当于在这两个一次式对应一次函数形成的下凸包中,取最小值点。由于我们发现这两个一次函数的斜率异号,所以破案了——最终的 q q q 就在这两个一次函数的交点处!

得到 q q q 后,就能得到该 f ( n , m ) f(n,m) f(n,m) q q q 的取值于不同的 n , m n,m n,m 大抵是不一样的,因此需要每次算。

时间复杂度 O ( n m ) O(nm) O(nm)

CODE

记忆化搜索,但其实完全没必要。

//真的猛士,敢于将打到一半的代码提交
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 1005
#define LL long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
//#pragma GCC optimize(2)
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
DB dp[1005][1005];
bool f[1005][1005];
DB DP(int x,int y) {
	if(!x || !y) return 1.0/(y+1);
	if(f[x][y]) return dp[x][y];
	f[x][y] = 1;
	DB k1 = DP(y,x-1),b1 = 1.0-k1;
	DB k2 = -1.0/(y+1),b2 = (1.0-DP(y-1,x))*y/(y+1) - k2;
	DB q = (b1-b2) / (k2-k1);
	return dp[x][y] = k1*q+b1;
}
int main() {
	n = read();m = read();
	DB ans = DP(n,m);
	printf("%.13f %.13f\n",ans,1-ans);
	return 0;
}

你可能感兴趣的:(数学)