数据:
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
Input
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。
Output
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
Sample Input
2 1 8 4 4 7
Sample Output
0 1 0
分析:两堆石子为x和y(若x<y) 先手必败状态:当且仅当(y−x)∗(sqrt(5)+1)/2==x时;
![](https://i-blog.csdnimg.cn/blog_migrate/1f6d93bd480264d0dd32df9bca678f71.jpeg)
Extend:(异或)
异或:首先是逻辑运算;
异或法则:‘异’就是不同为1,相同为0
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0异或0=0,1异或0=1,0异或1=1,1异或1=0,这些法则与加法是相同的,只是不带进位。
“⊕”是异或(xor)的运算符..... (以前遇到过,不认识  ̄□ ̄||...这里字号调大一点...)
![](https://i-blog.csdnimg.cn/blog_migrate/7026881f22ba96bd1793a86252076733.png)
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int N=1e5+10;
int main()
{
ll a,b;
while(~scanf("%lld %lld",&a,&b))
{
// if(a>b)
// {
// a^=b;
// b^=a;
// a^=b;
// }
if(a<b)
{
ll t=a;
a=b;
b=t;
}
ll k=(ll)((a-b)*(sqrt(5.0)+1)/2.0);
if(b==k)printf("0\n");
else printf("1\n");
}
return 0;
}
///威佐夫博弈
尼姆游戏 Nim Game
题目大意:给定 N NN 堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。
解题思路:必败状态:a1 ^ a2^ …^an=0 必胜状态:a1 ^ a2 ^ …^an=k (k!=0) 先手取走若干石子后,就转必胜状态为必败状态
若a1 ^ a2 ^ … ^ an!=0,一定存在某个合法的移动:将 ai 改变成ai’ 后满足a1 ^ a2 ^ … ^ ai ‘^ … ^ an=0
巴什博弈 Bash Game
题目大意:本游戏是一个二人游戏;有一堆石子 一共有n个;两人轮流进行;每走一步可以取走1…m个石子;最先取光石子的一方为胜;
分析:若n%(m+1)==0,则一定后手赢,因为不管先手拿多少个x(小于等于m,大于等于1),后手只要拿(m+1)-x 就好了..
威佐夫博弈 Wythoff Game
题目大意:有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。
分析:两堆石子为x和y(若x<y) 先手必败状态(奇异局势):当且仅当(y−x)∗(sqrt(5)+1)/2==x时;
斐波那契博弈(一)
题目大意:这是一个二人游戏;一共有3堆石子,数量分别是m, n, p个;
两人轮流走;每走一步可以选择任意一堆石子,然后取走f个;f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);最先取光所有石子的人为胜者;
分析:SG(x)=mex(SG(所有通过x能达到的”局势“)),那么对于n堆石子的取石子游戏,
若SG(1)^SG(2)^……^SG(n)==0,则先手必败,否则先手必胜。
这一题的各个状态的SG值是:因为每次可以取走斐波那契数列里的数的石子数,那么对于任意状态i,i-Fibonacci[j]都是可以取到的,故我们需要预处理出Fibonacci数列,然后通过枚举的方法,把i-Fibonacci[j]的SG值全部标记,扫一遍,找到最小的非负整数即为i的SG值
const int N=1e3+10;
int f[20];///斐波那契数列 到16就超过1000了~ m,n,p(1<=m,n,p<=1000)
int sg[N],vis[N];///vis数组记录所有出现过的非负整数
int m,n,p;
int getsg(int x) { ///求sg值
f[0]=1,f[1]=1;
for(int i=2; i<=20; i++)
f[i]=f[i-1]+f[i-2];
for(int i=1; i<=1000; i++) {
mem(vis,0);
for(int j=0; f[j]<=i; j++)
vis[sg[i-f[j]]]=1;
for(int j=0; j<=1000; j++) { ///求得最小非负整数
if(vis[j]==0) {
sg[i]=j;
break;
}
}
}
}
int main() {
getsg(N);
int n,m,p;
while(~scanf("%d%d%d",&n,&m,&p)) {
if(!n&&!m&&!p)break;
int ans=sg[n]^sg[m]^sg[p];
if(ans)printf("Fibo\n");
else printf("Nacci\n");
}
return 0;
}
斐波那契博弈(二)
题目大意:有一堆个数为n(n>=2)的石子,游戏双方轮流取石子;
先手不能在第一次把所有的石子取完,至少取1颗;
之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍。取走最后一个石子的人为赢家,求必败态。
结论:任何正整数可以表示为若干个不连续的Fibonacci数之和,当n为Fibonacci数的时候,必败。f[i]:1,2,3,5,8,13,21,34,55,89..
SG
(Sprague−Grundy)定理:所有一般胜利下的公平组合游戏都能转化成尼姆数表达的尼姆堆博弈,一个博弈的尼姆值定义为这个博弈的等价尼姆数;
对于当前游戏 X,它可以拆分成若干个子游戏 x 1 , x 2 , . . . , xn 那么 SG ( X ) = SG(x1) ⊕ SG(x2) ⊕ . . . ⊕ SG (xn)
分析:对于由 n个有向图游戏组成的组合游戏 设它们的起点分别为 s 1 , s 2 , . . . , s n
好多个起点,有好多个博弈图,也就是有好多个;
则当且仅当SG(s1)⊕SG(s2)⊕...⊕SG(sn) =0时,这个游戏为先手必胜 。
Anti-Nim游戏
大意:有 n 堆石子,两个人可以从任意一堆石子中拿任意多个石子(不能不拿),拿走最后一个石子的人失败。问谁会胜利
分析:先手必胜当且仅当:
∀ 所有堆的石子数都为 1且游戏的SG值为 0。
∃ 有些堆的石子数大于 1且游戏的SG值不为 0 。
题目大意:桌子上有n堆石子,小约翰和他的哥哥轮流取石子,每个人取的时候,可以随意选择一堆石子,在这堆石子中取走任意多的石子,但不能一粒石子也不取,我们规定取到最后一粒石子的人算输。
const int N = 2e5 + 7;
int n, m, t, k;
int a[N];
int main() {
scanf("%d", &t);
while(t -- ) {
scanf("%d", &n);
int sg = 0;
bool flag = 0;
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
sg ^= a[i];
if(a[i] > 1)
flag = 1;
}
if((flag == 0 && sg == 0) || (flag == 1 && sg != 0)) {
puts("John");
} else puts("Brother");
}
return 0;
}
Anti-SG游戏
定义 SJ 定理:对于任意一个 Anti - SG 游戏,如果我们规定:当局面中所有的单一游
戏的SG值为 0 时,游戏结束,
分析:则先手必胜当且仅当:
游戏的 SG 函数值不为 0 且游戏中某个单一游戏的 SG 函数值大于 1。
游戏的SG函数值为 0 且游戏中没有任意一个单一游戏的SG函数值大于 1 。
Multi - SG游戏
定义:游戏规定,在符合拓扑原则的前提下,一个单一游戏的后继可以为 多个单一游戏 。
Multi-SG其他规则与SG游戏相同。
每次操作能将一个当前的单一游戏分为多个单一游戏,也就是将当前这个堆石子分为多堆石子的特殊游戏。
对于一个状态来说,不同的划分方法会产生多个不同的后继,而在一个后继中可能含有多个独立的游戏
一个后继状态的SG值即为后继状态中所有独立游戏的异或和
该状态的 SG 函数值即为后继状态的 SG 函数值中未出现过的最小值
题意:给定n堆石子,两人轮流操作,每次选一堆石子,取任意石子或则将石子分成两个更小的堆(非0),取得最后一个石子的为胜。
打表
const int N = 50007, M = 507;
const int INF = 0x3f3f3f3f;
int sg[N];
bool vis[M + 7];
int main() {
sg[0] = 0, sg[1] = 1;
for (int i = 2; i < M; ++ i) {
memset(vis, 0, sizeof(vis));
//操作一,至少取一个
for (int j = 1; j <= i; ++ j)
vis[sg[i - j]] = 1;
//操作二,分成两堆,不为空
for (int j = 1; j < i; ++ j)
vis[sg[j] ^ sg[i - j]] = 1;
int j = 0;
while (vis[j]) j ++ ;
sg[i] = j;
}
for (int i = 1; i <= M; ++ i)
printf("sg[%d] : %d\n", i, sg[i]);
return 0;
}
AC:
int n, a[N], sg[N];
int main() {
int t;
scanf("%d", &t);
while(t -- ) {
int ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);
for(int i = 1; i <= n; ++ i) {
if(a[i] % 4 == 0) sg[i] = a[i] - 1;
else if(a[i] % 4 == 3) sg[i] = a[i] + 1;
else sg[i] = a[i];
ans ^= sg[i];
}
if(ans == 0) puts("Bob");
else puts("Alice");
}
return 0;
}
题目大意:游戏中有 n堆石子,每次行动可以选择:取走某堆的任意数量的石子(不可不取)。将石子拆分成三堆(三堆都不可为空)。最后取走为胜,问先手胜还是后手。
//操作二,分成三堆不为空
for (int j = 1; j <= i; ++ j)
for (int k = j; k <= i; ++ k)
if ((j + k) < i)
vis[sg[k] ^ sg[j] ^ sg[i - j - k]] = 1;