一、巴什博弈(Bash game)
1、概念
巴什博弈是一种较为简单的减法博弈(Subtraction game),减法博弈的共同特征为玩家轮流从某一总数(对应n件物品)中减去某个数值(对应拿取物品),所减去的数值限定在某个集合中(对应1到m),先将数值减为0者(先拿完物品者)获胜。
2、常见的形式
a.有一堆总数为n的物品,2名玩家轮流从中拿取物品。每次至少拿1件,至多拿m件,不能不拿,最终将物品拿完者获胜。
b.两人轮流报数,先报数的必须报1到m之间的正整数(包含1或m),后面所报数则必须比前一个人所报数大1到m(包含1或m),先说出n者获胜。
3.对应策略
在先取完者胜的巴什博弈中,若n可被m+1整除,则后手方必胜,否则先手方必胜。
原理如下:
先考虑最简单的局面
n<m+1,先手必胜;n=m+1,后手必胜;
设n=k(m+1)+r(所有的n),其中0<=r<=m;
a.如果r=0,n=k(m+1),若先手取x个,则后手可以对应取(m+1-x)个,则一直处于后手必胜的局面;
b.如果r不等于0,n=k(m+1)+r,则处于先手必胜的局面;
4.核心代码+例题
题目要求:
各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;
如果游戏的双方使用的都是最优策略,请输出哪个人能赢。
输入:
输入数据首先包含一个正整数C(C<=100),表示有C组测试数据。
每组测试数据占一行,包含两个整数n和m(1<=n,m<=1000),n和m的含义见题目描述。
输出:
如果先走的人能赢,请输出“first”,否则请输出“second”,每个实例的输出占一行。
样例:
2
23 2 first
4 3 second
#include<bits/stdc++.h>
using namespace std;
int n,t,m;
int main()
{
cin>>t;
while(t--)
{
cin>>n>>m;
if(n%(m+1)==0)
{
printf("second\n");//后手必胜
}
else
{
printf("first\n");//先手必胜
}
}
return 0;
}
二、尼姆博弈
1.概念
尼姆博弈中涉及到n堆不同的物品,这些堆中各自物品的数量是任意的。两名玩家在轮流行动时,可以选择将某一堆中任意数量的物品拿走,至少1个,至多全部拿走,但不能不拿或跨堆拿取物品。根据规则拿到最后一个物品,使得对手无物品可拿的玩家获胜。
2.常见形式
n堆若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
3.对应策略
若物品堆的尼姆和为0,则后手有必胜策略,否则先手有必胜策略
实现步骤
1.计算尼姆和sum;
2.尼姆和sum=0,则后手必胜
3.尼姆和sum不等于0,则计算sum与每一个物品堆xi的数量的异或值,即:sum⊕xi,直到找到一个(sum⊕xi)<xi的物品堆,从中拿取
xi-(sum⊕xi)个物品数量,方可以处于先手必胜的局面。
4.延伸(若拿到最后一个物品者必败)
如果将规则改为拿到最后一个物品者败,可得到尼姆博弈的一种变体。有下面的结论:
-
若尼姆和为0且所有堆中仅有1个物品,则先手必胜策略;
-
若尼姆和不为0且至少有一堆物品数量大于1,则先手有必胜策略;
-
否则后手方有必胜策略。
5.核心代码+例题
题目1:
一年在外 父母时刻牵挂
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧
陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐
如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
――“先手的人如果想赢,第一步有几种选择呢?”
输入:
输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1<M<=100),表示扑克牌的堆数,紧接着一行包含M个整数Ni(1<=Ni<=1000000,i=1…M),分别表示M堆扑克的数量。M为0则表示输入数据的结束。
输出:
如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。
样例:
Sample
Inputcopy | Outputcopy |
---|---|
3 5 7 9 0 | 1 |
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=110;
int n;
int a[N];
void init()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
signed main()
{
init();
while(cin>>n&&n!=0)
{
int i,num;
int f=0;
int sum=0;
for(i=1;i<=n;i++)
{
cin>>a[i];
sum^=a[i];
}
if(sum==0)
{
cout<<"0"<<'\n'; //后手必胜
}
else
{
int cnt=0;//先手能赢,第一步可行的方案数,cnt记录方案数量
for(i=1;i<=n;i++)
{
if(a[i]>(a[i]^sum))//(ps:注意>的优先级大于^)
{
cnt++;
}
}
cout<<cnt<<'\n';
}
}
return 0;
}
/*
1.先求出所有堆异或后的值sum,再用sum对每一堆进行异或
2.ans=sum^a[i];
如果ans<a[i],即sum^a[i]<a[i];
相当于当前玩家从a[i]中取走(a[i]-ans)个;
*/
题目2:
m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
输入:
输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.
输出:
先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.
Sample
Inputcopy | Outputcopy |
---|---|
2 45 45 3 3 6 9 5 5 7 8 9 10 0 | No Yes 9 5 Yes 8 1 9 0 10 3 |
思路:
1.先手输,输出No;
2.先手胜利,输出Yes,然后输出胜利的方案
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int a[N];
int n;
void init()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int main()
{
init();
while(cin>>n&&n!=0)
{
int i;
int sum=0;
for(i=1;i<=n;i++)
{
cin>>a[i];
sum^=a[i];
}
if(sum==0)
{
printf("No\n");
}
else
{
printf("Yes\n");
int ans=0;
for(i=1;i<=n;i++)
{
if(a[i]>(sum^a[i]))
{
int p=(sum^a[i]);//取走后剩下的数量
printf("%d %d\n",a[i],p);
}
}
}
}
return 0;
}
三、威佐夫博弈
1.概念
威佐夫博弈(Wythoff's game):有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
2.常见形式
游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。
3.对应策略
两个人如果都采用正确操作,那么面对非奇异局势,先手必胜;反之,后手必胜
a.黄金分割数:1.618=(√5+1)/2
1.618=(sqrt(5.0)+1)/2
b.公式:
double ans=(int)((b-a)*((sqrt(5.0)+1)/2));
4.核心代码+例题
题目:
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
输入:
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。
输出:
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
Sample
Inputcopy | Outputcopy |
---|---|
2 1 8 4 4 7 | 0 1 0 |
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
double a,b;
while(cin>>a>>b)
{
if(a>b)
{
swap(a,b);
}
double ans=(int)((b-a)*((sqrt(5.0)+1)/2));
if(a==ans)
{
cout<<"0"<<'\n';
}
else
{
cout<<"1"<<'\n';
}
}
return 0;
}