博弈论一般是通过讨论必胜态和必败态来确定输赢的,下面我将通过这种方法来引入一些常用博弈论结论。
巴什博弈:一堆物品有n个,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
分析:
如果n<m+1,则先手可以一次取完所有的物品,先手必胜
如果n=m+1,则先手无论取几个物品,都能保证后手一次取完剩余的物品,也就是说先手必败。
对于n>m+1,一定有n=k *(m+1)+ r
若r!=0,先手可以第一次取完r个物品,这样对于后手取k个物品,先手总能取(m+1-k)个物品来使得剩余物品是m+1的倍数,也就是维持到了后手的必败态,从而先手必胜。
若r=0,则先手取k个物品,后手总能取(m+1-k)个物品来使得剩余物品是m+1的倍数,也就是维持到了先手的必败态,从而后手必胜。
总的来说:如果n是m+1的倍数,那么先手必败,反之先手必胜,下面是判断代码:
#include<cstring>
int main
{
int n,k;
cin>>n>>k;
if(n%(k+1)==0) printf("后手必胜");
else printf("先手必胜");
return 0;
}
斐波那契博弈:一堆石子有n个,两人轮流取,先取者第一次可以去任意多个,但是不能取完,以后每次取的石子数不能超过上次取子数的2倍。取完者胜。
先给出结论:先手胜当且仅当n不是斐波那契数。
先说一下为什么当n是斐波那契数时先手必败吧:
这个我们用数学归纳法证明:
设n=f[i] (第i个斐波那契数)
当n=f[3]=2时,显然先手必败
不妨假设当i<=k时都有先手必败。
则当i=k+1时,有n=f[k+1]=f[k]+f[k-1]
我们把这一堆n个石子分成两堆,一堆石子数目是f[k-1]个,另一堆石子数目是f[k]个。
如果先手第一次取的石子数目大于等于f[k-1]个,由于f[k]=f[k-1]+f[k-2]<2*f[k-1],所以后手可以直接取完剩余石子从而直接获胜。
我们主要需要考虑的问题是最后取完石子数目是f[k-1]的那一次取的石子数目是多少,而最后取的石子数目又取决于前一次所取的石子数目
我们先考虑一下极端情况,就是先手第一次直接取f[k-1]/3个石子,这样后手直接取2*f[k-1]/3个石子,这样第一堆石子就已经取完了,我们先来看一下此时后手能不能一次把石子数目是f[k]的一堆石子全部取完,也就是比较4*f[k-1]/3与f[k]的大小,显然是f[k]要大于4*f[k-1]/3,也就是说在这种情况下先手无法一次把石子数目是f[k]的一堆石子全部取完,这也就等价于i=k的情况了,即先手一次无法将石子取完,石子的总数目为f[k],若先手第一次取的石子数目在f[k-1]/3~f[k-1]之间,不妨假设为x,则后手可以选择取f[k-1]-x个石子从而实现把第一堆石子取完,从而问题又转化为了i=k的情况了,到这我们也就证明了当n是斐波那契数时先手必败了。
下面我们来看一下为什么当n不是斐波那契数时先手必胜:
在这引入一个定理(Zeckendorf定理):任何正整数都可以表示成若干个不连续的斐波那契数(不包括第一个斐波那契数)之和。
比如96=55+21+13+5+2
因此n=f[x1]+f[x2]+……+f[xn](x1>x2>……>xn)
对于任意的i>j+1都有f[i]>2*f[j],我们令先手第一次取完f[xn],那么后手无法一次取完f[xn-1],我们就可以保证使后手陷入当前这堆石子的必败态(石子数目是斐波那契数)。从而我们就证明了先手的必胜策略。
f[0]=f[1]=1;
while(f[i-1]<n)
{
if(f[i-1]+f[i]==n)
return true;
i++;
}
return false;
尼姆博弈:有n堆物品,两人轮流取,每次取某堆中不少于1个,最后取完者胜。
先说一下结论:我们先将n堆物品进行按位异或,若异或后结果为0,则先手必败,反之先手必胜。
下面给出若n堆石子异或后结果不为0时的先手必胜策略:
不妨设10堆石子异或后的结果为0111001100,那么先手第一步应该怎么操作呢?由异或结果的第九位为1可知一定存在一堆物品满足物品数目的二进制拆分中第九位为1,我们将这堆物品的数目与0111001100异或后得到的结果必然小于这堆物品的原数目(因为异或后的结果第9位值变成了0).不妨假设异或后值为0011010100,那么我们就可以从这堆物品中选取物品使得该堆物品的数目减少至0011010100,这样选完后剩余物品的异或值就变成了0000000000,每次都这样操作,就可以维持这种平衡态,从而保证先手必胜。
显然,当初始时n堆物品异或值为0,那么无论先手如何选取都会打破这种平衡态,从而使得后手进入必胜态。
代码:
#include<cstring>
int main
{
int ans=0,n;
cin>>n;
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
ans^=t;
}
if(ans) printf("先手必胜");
else printf("后手必胜");
return 0;
}
威佐夫博弈:有两堆各若干物品,两个人轮流从任意一堆中至少取出一个或者从两堆中取出同样多的物品,规定每次至少取一个,至多不限,最后取光者胜。
先来分析两组数据:
第一组:(1,2)
若先手取第一堆的一个,则后手直接取完第二堆,后手胜。
若先手取第二堆的一个,那么后手同时从两堆中各取一个,则变成了(0,0),也是后手胜
若先手取第二堆的两个,那么后手直接取第一堆一个,还是后手胜
所以对于这组数据是必败态。
第二组:(3,5)
若先手取第一堆的一个,则后手取第二堆的4个,于是(3,5)——>(2,1),由于这是必败态,所以后手胜
若先手取第一堆的两个,则后手取第二堆的3个,于是(3,5)——>(1,2),还是后手胜
若先手取第一堆的3个,则后手可以直接取完第二堆,所以后手胜
若先手取第二堆的1个,后手可以同时从两堆中取2个,于是(3,5)——>(1,2),后手胜
若先手取第二堆的2个,后手可以同时从两堆中取三个,于是(3,5)——>(0,0),后手胜
若先手取第二堆的3个,后手可以同时从两堆中取1个,于是(3,5)——>(2,1),后手胜
若先手取第二堆的4个,后手可以从第一堆中取1个,于是(3,5)——>(2,1),后手胜
若先手取第二堆的5个,则后手可直接取完第一堆获得胜利。
所以这组数据也是必败态。
先列出来几组比较小的必败态:
(1,2)
(3,5)
(4,7)
(6,10)
(8,13)
(9,15)
(11,18)
(12,20)
观察这些必输态,可以发现三个结论:
(1)相邻两组的差值每次增大1,第一组差值为1,第二组差值为2,……,第n组差值为n
(2)必败态中每个数都只出现过一次,且每个必败态的第一个值是之前必败态
从未出现过的最小自然数。
(3)每一个必败态中的较小数是他们差值的1.618倍向下取整。(最重要的结论)
于是随便给我们两个数我们都能通过判断两个数的差与两个数之间的较小的数之间的关系来判断这个状态是不是必败态
下面我们来思考这样一个问题,倘若给我们两个数m和n,这两个数不满足必败态的特征,我们能不能进行一次操作来使他们变成必败态呢?
答案是显然的,不过我们需要通过两种情况来讨论一下:(不妨假设m>n)
设x为 (m-n)*1.618向下取整后得到的值
第一种情况:n>x,则直接从两堆中同时取(n-x)个物品就可以使(n,m)——>(x,m-n+x),显然转换后的状态是一种必败态。因为较小的数x与他们差值的1.618倍向上取整相同
第二种情况:n<x:
若n没在之前的必败态中出现过,说明n一定是一种必败态中较小的值,而该必败态中较大的值与n的差值的1.618倍向下取整得到的值就是n,由于n<x,则m>n+n/1.618,即将m转变为n+n/1.618向下取整即可。
若n在之前的必败态中出现过,说明n一定是作为必败态中较大的值出现的,所以该必败态中较小的值一定小于m,直接将m变为那个必败态中较小的值即可实现到必败态的转变。
我把详细证明写在了一张纸上,有兴趣的小伙伴可以参考一下:
注意,我们一般不直接使用1.618,而是通过(sqrt(5.0) + 1) / 2得到。
下面是核心判断代码:
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
if(n>m) swap(n,m);
double e=(sqrt(5.0)+1)/2;
if(n==(int)((m-n)*e)) printf("先手必输");
else printf("先手必胜");
return 0;
}