P2197 【模板】Nim 游戏
\(\textrm{\textup{\textbf{\color{404040}洛谷 P2197 【模板】Nim 游戏}}}\)
↑↑标题可以点击↑↑
题目描述
甲,乙两个人玩 nim 取石子游戏。
nim 游戏的规则是这样的:地上有 \(n\) 堆石子(每堆石子数量小于 \(10^4\)),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 \(n\) 堆石子的数量,他想知道是否存在先手必胜的策略。
输入格式
本题有多组测试数据。
第一行一个整数 \(T\) (\(T\le10\)),表示有 \(T\) 组数据
接下来每两行是一组数据,第一行一个整数 \(n\),表示有 \(n\) 堆石子,\(n\le10^4\)。
第二行有 \(n\) 个数,表示每一堆石子的数量。
输出格式
共 \(T\) 行,每行表示如果对于这组数据存在先手必胜策略则输出 Yes,否则输出 No。
输入输出样例 #1
输入 #1
2
2
1 1
2
1 0
输出 #1
No
Yes
(这是一道代码实现十分简单的题目,就不废话,直接干吧!)
题目讲解
知识铺垫:
1. Nim 游戏基本概念
Nim 游戏是一种经典的组合博弈游戏。在 Nim 游戏中,有若干堆物品,两名玩家轮流操作,每次可以从任意一堆中取走任意数量的物品(不能不取),最后没有物品可取的玩家输掉游戏。
2. 异或运算异或运算
异或运算异或运算(用符号 \(\oplus\) 表示)是一种二进制位运算。对于两个二进制数,相同位上数字相同则结果为 \(0\),不同则结果为 \(1\)。例如:
- \(3\)的二进制表示为 \(011\) ,\(5\) 的二进制表示为 \(101\),则 \(3\oplus 5=110\)(二进制),转化为十进制是 \(6\) 。
异或运算具有以下性质:
- 交换律:\(a\oplus b=b\oplus a\) ;
- 结合律:\((a\oplus b)\oplus c=a\oplus(b\oplus c)\) ;
- 对于任意整数 \(a, a\oplus a=0\) ;
- 对于任意整数,\(a,a\oplus 0=a\) 。
3. Nim 和
在 Nim 游戏中,Nim 和是一个关键概念。对于\(n\)堆石子,每堆石子的数量分别为 \(a_1,a_2,a_3…a_{n-1},a_n\) ,它们的 Nim 和定义为 \(S=a_1 \oplus a_2 \oplus a_3… \oplus a_{n-1} \oplus a_n\)。
解答思路
1.必胜策略与 Nim 和的关系
Nim 游戏有一个重要的结论:当且仅当 Nim 和 \(S\ne 0\) 时,先手有必胜策略;当 \(S=0\) 时,先手必败。
证明思路:
- 终态:当所有堆的石子数都为 0 时,Nim 和 \(S=0\),此时轮到的玩家没有石子可取,输掉游戏。
- 状态转移:
若当前状态的 Nim 和 \(S \ne 0\),设\(S\)的二进制表示中最高位的 1 在第 \(k\)位。那么必然存在至少一堆石子,其数量的二进制表示中第\(k\)位也是 1(因为异或运算中只有对应位上有奇数个 1 结果才为 1)。设这堆石子数量为\(a_i\) ,我们可以从这堆石子中取走一些石子,使得剩下的石子数量为\(a_i{'}=a_i \oplus S\)。这样操作后,新的 Nim 和\(S'=(a_1 \oplus … \oplus a_i{'} \oplus a_i+1 \oplus …a_n)=S'=(a_1 \oplus … \oplus a_i\oplus S \oplus a_i+1 \oplus …a_n)=S \oplus S=0\) 。
若当前状态的 Nim 和\(S=0\),无论从哪一堆取走多少石子,都会使得新的 Nim 和 \(S' \ne 0\)。因为从某一堆\(a_j\)取走一些石子后,\(a_j\)变为\(a_j{'}\),且\(a_j \ne a_j{'}\) ,那么新的 Nim 和 \(S'=S \oplus a_j \oplus a_j{'}=0 \oplus a_j \oplus a_j{'}=a_j \oplus a_j{'} \ne 0\)。
代码实现:
码风可能不如人意,请见谅。
点击查看代码
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
using namespace std;
int t,n;
int sum=0;
int main(){
ios::sync_with_stdio(0);//cin 与 cout 的的加速语句
cin.tie(0),cout.tie(0);
cin>>t;
for(int x=1;x<=t;x++){
int y;
sum=0;
cin>>n;
for(int i=1;i<=n;i++) {
cin>>y;
sum^=y;//逐堆异或计算总和
}
cout<<(sum?"Yes\n":"No\n");
/*or
if(sum){
cout<<"Yes\n";
}else{
cout<<"No\n";
}
*/
}
return 0;
}

浙公网安备 33010602011771号