Acwing_891Nim游戏【博弈论】

题目描述:

给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数n。第二行包含n个数字,其中第 i 个数字表示第 i堆石子的数量。

输出格式

如果先手方必胜,则输出“Yes”。否则,输出“No”。

数据范围

1≤n≤105,1≤每堆石子数≤109

输入样例:

2
2 3

输出样例:

Yes

思路分析:

先给出Nim游戏的一些概念和结论:

  • 给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。

  • 我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。

  • 所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
    NIM博弈不存在平局,只有先手必胜和先手必败两种情况。

定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0

也就是说,当各堆石子的个数a1 ^ a2 ^ … ^ an = 0时,先手必败,否则先手必胜。我们先直观的去理解这个定理,对于第i堆石子,其石子个数为x,在x的二进制表示中,至多能为每位贡献一个1.。比如32位的整数,每一位至多有一个1,这是显然的。a1 ^ a2 ^ … ^ an = 0说明了在二进制的各位,1出现的次数都是偶数的。先手不论怎么拿,对二进制的各位而言,要么不影响某位1的总数,要么让某位1的总数多1或者少1。而后手只需要从某堆中拿的石子个数使得石子堆中各位1的总和恢复成偶数即可,由于各位1的总数开始是偶数,先手不可能将该位的1拿完(除非先手面临的就是必败态:石子已经被拿完了),所以后手永远有石子可拿,因此先手必败。不妨举个例子,为了表示方便,石子的个数都用二进制表示。设石子一共有3堆,个数分别为1111,1001,0110,可见初始状态的异或和是0。假设先手先从第一堆里拿走了1100,第一堆还剩0011,此时二进制中各位1的个数从左往右分别是1,1,2,2.。为了让前两位的1的个数恢复偶数,后手先想到从某堆里拿走1100,即与先手进行同样的操作,但是发现此时各堆石子的数目0011,1001,0110,没有哪个石子堆可以满足后手的需要,于是后手就想将二进制中1的个数变为0,2,2,2。即从第二堆石子中拿出100个石子,则第二堆还剩0111个石子。此时二进制各位1的总数又恢复偶数了,重复这类操作,直至先手面临的状态是0,0,0,0,即必败态。上面不是对这个定理进行严格的证明,只是想从直观的角度去解释这个定理。

下面正式证明该定理:
首先必败态,没有石子了,异或和是0。然后对于任意一种异或和非0的情况,我们都可以用一步操作使之变为异或和为0的情况。设a1 ^ a2 ^ … ^ an = x != 0,设x二进制最高位的1是在第k位,则n堆石子中至少有一堆石子的个数该位为1,设第i堆石子个数ai第k位为1,则ai^x < ai,(将ai的第k为从1变成0了,低位无论怎么变也不会超过原来ai的大小)。此时我们从第i堆中拿走ai - ai^x个石子,则第i堆石子个数变为ai - (ai - ai^x)= ai ^ x个,故a1 ^ … ^ ai ^ x ^ … ^an = a1 ^ a2 ^ … ^ an ^ x = x ^ x = 0。对于任意一种异或和为0的情况,我们拿走任意数量的石子都无法使得石子的异或和还是0.假设从第i堆拿走一定数量的石子使得ai变为ai’,a1 ^ … ^ ai’ ^ … ^ an = 0,又a1 ^ … ^ ai ^ … ^ an = 0,将两个式子左右两边分别异或起来得ai’ ^ ai = 0,故ai’ = ai,也就是什么都不拿,这是违背规则的,故假设不成立。对于异或和为0的状态,我们无法使之一步转化为异或和还是0的状态。现在我们找到了最终的必败态是石子数量为0,异或和为0.只要先手面对的是异或和非0的状态,则可以通过一步操作使之转化为异或和为0的状态,后手面对异或和为0的状态无论怎么操作都只能转化为异或和非0的状态,因此只要先手每次都采用最优策略,面临必败态的一定是后手。

AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n;

int main()
{
	int res = 0;
	scanf("%d", &n);
	while (n--) {
		int x;
		scanf("%d", &x);
		res ^= x;
	}
	if (res) printf("Yes");
	else printf("No");

	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值