前言
总结一下最近碰到的博弈题
提示:以下是本篇文章正文内容,下面案例可供参考
一、巴什博弈
巴什博弈:一堆物品有n个,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
分析
显然当n=m+1时,后手必赢;因此当n=(m+1)*r+s(r为任意自然数,s≤m)时,先手能拿s个物品使得目前剩余物品变为n’=(m+1)*r时,后手取k个,先手就取m+1-k(k<=m)个,则接下来剩余的物品数变为n’’=(m+1) *(r-1),以后保持这样的取法,那么先取者肯定获胜。同理可得后手必赢的条件。
由上述可推出,只要物品数量满足n%(m+1)==0则后手胜利,否则先手胜利
变形
当我们规定,如果最后取光者输时,即取到最后一个物品的人输,此时只要物品数量满足(n-1)%(k-1)==0则后手必赢。
除了取物品,报数也可以运用此类博弈,例如两个人轮流报数,每次至少报一个,最多报m个,谁能报到n者胜。
代码如下(示例):
#include <iostream>
using namespace std;
int main()
{
int t,n,k;
while(~scanf("%d",&t)){
while(t--){
scanf("%d%d",&n,&k);
if(n%(k+1)==0)
printf("second win!\n");
else
printf("first win!\n");
}
}
return 0;
}
二、尼姆博弈
有m堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
分析
我们先来简化一下,假设有两堆若干个物品:若数量相同,无论先手拿多少,后手只需在另一堆拿和先手同样多的物品(此时后手必胜);若数量不同,则先手取其中一堆若干个至两堆数量相同(此时先手必胜)。
假设此时有三堆若干个物品,我们用(a,b,c)来表示某种局势:无论谁面对奇异局势,都必然失败,显然(0,0,0)为奇异局势;(0,n,n)是第二种奇异局势,此时与只有两堆相同数量物品相同;(1,2,3)也是奇异局势,无论自己如何拿,接下来对手都可以将其变为(0,n,n)的情形。
于是就有大佬想到了把这种规律和二进制联系在一起,至于是怎么联系的,再来一点点分析。
首先,每堆物品的数量一定是非负整数,就可以用二进制来表示。我们先假设有三堆石子(3,6,9),分别用二进制表示为11,110,1001
11=10+1;
110=100+10;
1001=1000+1;
再转换为十进制:3=2+1,6=4+2,9=8+1;所以现在可以看作是3堆变成了6堆,其中两堆为1,两堆为2,在这两堆里先手必输,而剩下两堆为4和8,此时先手从8个一堆中拿走4个即可,此时先手必胜。
从上述变形中我们可以发现先手在1,2这两堆中达成了(n,n)局面所以必输,有没有方法能在二进制的计算中将他们直接舍去呢?于是就用到了异或。
先来说下异或
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。并且异或运算会直接将十进制转化为二进制。
所以上述三堆的案例就可以用3^ 6^ 9!=0,所以先手必赢。
也就是说当有n堆石子时,
只要满足a^ b^ c^ d^…!=0,先手必赢。
代码如下(示例):
#include <iostream>
using namespace std;
int main()
{
long long m;
while((scanf("%lld",&m)!=EOF)&&m){
long long n[200000],i;
long long x=0;
for(i=0;i<m;i++){
cin>>n[i];
x=x^n[i];
}
if(x){
cout<<"Yes"<<endl;
}
else cout<<"No"<<endl;
}
return 0;
}
题目
链接: link.
代码如下(示例):
#include <iostream>
using namespace std;
int main()
{
long long m;
while((scanf("%lld",&m)!=EOF)&&m){
long long n[200000],i,t;
long long x=0;
for(i=0;i<m;i++){
cin>>n[i];
x=x^n[i];
}
if(x){
cout<<"Yes"<<endl;
for(i=0;i<m;i++){
t=x^n[i];
if(t<n[i])
cout<<n[i]<<" "<<t<<endl;
}
}
else cout<<"No"<<endl;
}
return 0;
}