4.1 KiB
一、题目描述
现在,有一个 n
级台阶的楼梯,每级台阶上都有若干个石子,其中第 i
级台阶上有 a_i
个石子(i≥1
)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 n
。
第二行包含 n
个整数,其中第 i
个整数表示第 i
级台阶上的石子数 a_i
。
输出格式
如果先手方必胜,则输出 Yes
。
否则,输出 No
。
数据范围
1≤n≤10^5,1≤a_i≤10^9
输入样例:
3
2 1 3
输出样例:
Yes
二、结论
如果 奇数 阶台阶的石子个数 异或值不是零,则 先手必胜。
如果 奇数 阶台阶的石子个数 异或值是零, 则 先手必败。

这是一个台阶Nim
的初始状态 2
,1
,3
,2
,4
, 只能把大号台阶上的石子往小号台阶上放。
其实台阶Nim
经过转换可以变为经典Nim
:
把石子从奇数堆移动到偶数堆可以理解为拿走石子,就相当于几个奇数堆的石子在做
Nim
。 如所给样例,2(台阶1) ∧ 3(台阶3) ∧ 4(台阶5)= 5
不为零,所以先手必胜
三、策略
我们可以把每级奇数级台阶上的石子 看做 经典Nim
游戏中的每一堆。
然后,我们只需要模拟出经典Nim
游戏中的操作:
- 把石子拿掉,就相当于把某奇数级台阶上的若干石子挪到下面的偶数级台阶上
(拿下地面更好,这也是选奇数级不选偶数级的主要原因)
- 如果对手把上面偶数级台阶上的石子挪下来,也没关系,顺水推舟,把它再挪到下一层偶数台阶就行了
四、实现代码
#include <bits/stdc++.h>
using namespace std;
int n, res;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (i % 2) res ^= x;
}
if (res)
puts("Yes");
else
puts("No");
return 0;
}
### 五、问题与解答
Q1:
为什么可以这样来转化?
答:假设我们是先手,所给的台阶石子状态的奇数堆做Nim
先手能必胜,我就按照能赢的步骤将奇数堆的石子(a_i-(x ∧ a_i)
个)移动到偶数堆(相当于扔掉)。
-
如果对手也是移动奇数堆,我们继续移动奇数堆。
-
如果对手将偶数堆的石子移动到了奇数堆,那么我们紧接着将对手所移动的这么多石子从那个奇数堆移动到下面的偶数堆,两次操作后,相当于偶数堆的石子向下移动了几个,而奇数堆依然是原来的样子,即为必胜的状态。 就算后手一直在移动偶数堆的石子到奇数堆,我们就一直跟着他将石子继续往下移,保持奇数堆不变,如此做下去,我可以跟着后手把偶数堆的石子移动到
0
,然后你就不能移动这些石子了。所以整个过程,将偶数堆移动到奇数堆不会影响奇数堆做
Nim
博弈的过程,整个过程可以抽象为奇数堆的Nim
博弈。
其他的情况,先手必输的,类似推理,只要判断奇数堆做Nim
博弈的情况即可。
Q2:
为什么只对奇数堆做Nim
就可以,而不是偶数堆呢?
答:因为如果是对偶数堆做Nim
,对手移动奇数堆的石子到偶数堆,我们跟着移动这些石子到下一个奇数堆,那么最后是对手把这些石子移动到了0
,我们不能继续跟着移动,就只能去破坏原有的Nim
而导致胜负关系的不确定,所以只要对奇数堆做Nim
判断即可知道胜负情况。