有一副互不相同的牌共 n + m + 1 n+m+1 n+m+1 张,编号 1 ∼ n + m + 1 1\sim n+m+1 1∼n+m+1。有两个人,第一个人 n n n 张,第二个人 m m m 张。另外有一张牌放在桌子上。
两个人玩游戏轮流操作(其中第一个人为先手)。有如下 2 2 2 中操作类型:
猜测:猜桌上的那张牌是什么。如果猜对了则获胜,猜错了则失败。操作完之后游戏结束。
指定:报一张牌的名字,如果对方手上有这张牌,则将该牌丢弃,游戏继续;如果对方手上没有这张牌,对方则会表示他不用由此牌。
现在假设两个人都知道自己的牌分别是什么,但是不知道桌上和对方手里的牌具体是什么。
若双方都采取最优策略进行游戏,问先手和后手获胜概率。
若选手答案与标准答案的误差不超过 1 0 − 9 10^{-9} 10−9 则认为正确。
数据范围: 0 ≤ n , m ≤ 1000 0\le n,m\le 1000 0≤n,m≤1000 。
虽然是采取最优策略进行的,但是往往没有必胜策略。这里所说的最优策略是指,让获胜概率最大化。
不难发现,当角色丢弃一张牌后,又变成了一个总牌数更少的、先后手交换的起始局面,因此可以设 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 行开始。
不(挺)难发现,「指定」有两种方式:
于是,围绕着先手「交流」或「欺骗」,后手「信任」(意味着下一步「猜测」)或「不信」,就有这样的状态转移表:
后 手 先 手 「 交 流 」 「 欺 骗 」 ( 丢 弃 ) 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(1−f(m−1,n))0m+11「欺骗」−11−f(m,n−1)
由于指定与猜测结果的随机性很大,所以决策上,先后手没有必胜策略,也会选择以一定概率进行「交流」/「欺骗」,或「信任」/「不信」。我们设先手选择「交流」的概率为 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+(1−q)(1−f(m,n−1)))+(1−p)(−m+1q+m+11+m+1m(1−f(m−1,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(1−f(m−1,n)) p=1:f=q+(1−q)(1−f(m,n−1))
那么,就相当于在这两个一次式对应一次函数形成的下凸包中,取最小值点。由于我们发现这两个一次函数的斜率异号,所以破案了——最终的 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) 。
记忆化搜索,但其实完全没必要。
//真的猛士,敢于将打到一半的代码提交
#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;
}