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;
}
posted @ 2025-03-26 21:32  wujunxi206  阅读(51)  评论(0)    收藏  举报