P4279 [SHOI2008]小约翰的游戏(Anti_nim)
题面
题目描述
小约翰经常和他的哥哥玩一个非常有趣的游戏:桌子上有 \(n\) 堆石子,小约翰和他的哥哥轮流取石子,每个人取的时候,可以随意选择一堆石子,
在这堆石子中取走任意多的石子,但不能一粒石子也不取,我们规定取到最后一粒石子的人算输。小约翰相当固执,他坚持认为先取的人有很大
的优势,所以他总是先取石子,而他的哥哥就聪明多了,他从来没有在游戏中犯过错误。小约翰一怒之前请你来做他的参谋。
自然,你应该先写一个程序,预测一下谁将获得游戏的胜利。
输入格式
本题的输入由多组数据组成,第一行包括一个整数 \(T\)(1≤T≤500),表示输入总共有 \(T\) 组数据。
每组数据的第一行包括一个整数 \(N\)(1≤N≤50),表示共有 \(N\) 堆石子,接下来有 \(N\) 个不超过 5000 的整数,分别表示每堆石子的数目。
输出格式
对于每组数据,如果约翰能赢得比赛,则输出 John,否则输出 Brother,请注意单词的大小写。
输入输出样例
输入 #1
2
3
3 5 1
1
1
输出 #1
John
Brother
说明/提示
对于 40% 的数据,T≤250
对于 100% 的数据,T≤500
题解
裸的Anti_nim 板子题。
Anti_nim 和普通的 nim 游戏的区别是取走最后一个的人输。
我们定义一下几种状态
T 态 亦或和为0
S 态 亦或和不为0
孤单堆 一堆仅有一个火柴
充裕堆 一堆有大于等于一根的火柴
T0 态 T态中充裕堆为0的状态
T2 态 T态中充裕堆数大于等于二的状态
S0 态 S态中充裕堆数位0的状态
S1 态 S态中充裕堆数为一的状态
S2 态 S态中充裕堆数大于等于二的状态
注:没有T1态,因为因为只有一个充裕堆时 其最高位对应>1 不会被异或消掉,也就是充裕堆会影响高位
引理1:
S0态必败,T0态必胜
这个就和普通的 nim 游戏一样
引理2:
S1态必胜
1.孤单堆为奇数时,拿走充裕堆就变成了 S0态
2.孤单堆为偶数时,那就把充裕堆拿成孤单堆,在重复情况1
引理3:
S2态不可能转化为 T0态
因为S2态至少有两个充裕堆,取一次最多减少一个充裕堆,但T0态没有一个充裕堆,所以不符合题意。
引理4:
S2态只要方法正确,可以一次性变成T2态。
S2态不能变成T0 态,又没有 T1 态,
又根据 S态能转化为 T 态,所以 S2 态只能转化为 T2 态
引理5
T2态只能转化为 S1态和S2态
首先T2不能转化为 S0态,因为两个充裕堆你没法一起取走。
假设我们T2态有两个充裕堆,取走一个就变成了一个也就是S1态。
有大于二个充裕堆时,取走之后充裕堆数大于等于一也就是 S2态。
引理6
S2态必胜
-
先手把S2态变成T2态
-
后手只能把T2态变为S1或S2态,变为S1态后手必败,先手必胜。变为S2态,先手可以重复步骤一。
所以S2态必胜
引理7
T2态必败
T2态只能转化为 S1态和 S2态,但那两个都是必胜态,所以T2必败
必胜态 T0,S1,s2
必败态 S0,T2
观察规律发现
1.每一堆石子只有一个时 且异或和为0
2.存在至少一堆石子多于一个时 且异或和不为0
先手必胜
SJ定理
定义 Anti_nim
决策集合为空时 当前者胜,其余同SG游戏
先手胜利,当且仅当
1.游戏SG函数不为0且某个单一游戏SG值大于1
2.游戏SG函数为0且任意单一游戏SG值=1
然后,这道题就变成了模板题
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int T,n,ans,cnt1,cnt2,num[55];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int main()
{
T = read();
while(T--)
{
n = read(); ans = 0; cnt1 = 0, cnt2 = 0;
for(int i = 1; i <= n; i++)
{
num[i] = read();
ans ^= num[i];
if(num[i] > 1) cnt1++;
}
// cout<<ans<<" "<<cnt1<<endl;
if(ans == 0 && cnt1 == 0) printf("John\n");
if(ans != 0 && (cnt1 == 1 || cnt1 >= 2)) printf("John\n");
if(ans != 0 && cnt1 == 0) printf("Brother\n");
if(ans == 0 && cnt1 >= 2) printf("Brother\n");
}
return 0;
}