文章目录
博弈论(Nim游戏)
文章首发于我的个人博客:欢迎大佬们来逛逛
Nim游戏
题目要求:给你 n 堆石子,两个人轮流取石子,每次选择一堆,可以取出这一堆的任何数量的石子,直到某个人无法再取出任何石子,则另一个人就赢了。
例如:n=2 :1 1
第一个人取第一堆,第二个人取第二堆,则第一个人必输。
n=2: 1 0
第一个人取第一堆,则第一个人必赢。
可以看出,如果出现:
- a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n \ne 0 a1⊕a2⊕a3...⊕an=0 :则是一个必胜态
- a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n = 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n = 0 a1⊕a2⊕a3...⊕an=0:则是一个必输态
为什么?
考虑下面的案例:
n=2: 2 2
他们的异或值为 0,可以证明对于甲来说这一定是一个必输态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScWBisVX-1685532902668)(%E5%8D%9A%E5%BC%88%E8%AE%BA%EF%BC%88Nim%E6%B8%B8%E6%88%8F%EF%BC%89%206b52c5f8a3dc43649a90751bf2e19e50/Untitled.png)]
n=2: 2 3
他们的异或值不为0,则可以证明这一定是一个必胜态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hVFJ3QkB-1685532902669)(%E5%8D%9A%E5%BC%88%E8%AE%BA%EF%BC%88Nim%E6%B8%B8%E6%88%8F%EF%BC%89%206b52c5f8a3dc43649a90751bf2e19e50/Untitled%201.png)]
其实对于这个必胜态的过程,我们发现其实就是把必输态转移到了乙的身上.
则通过上面的描述,一个必胜态一定可以转换为必输态。
定理如下:
- 必胜态的后继状态至少存在一个必输态。
如果是必胜态,则一定存在 a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n = s ≠ 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n =s \ne 0 a1⊕a2⊕a3...⊕an=s=0 ,则假设 s s s 的最高位的二进制1 是第 k k k 位,则在原始序列 a 1 . . a 2 . . . a i a_1 .. a_2 ... a_i a1..a2...ai 中的第 k k k 位一定存在奇数个1。用 a i ⊕ s a_i \oplus s ai⊕s 去替换 a i a_i ai 则会存在以下的式子(这里假设是 a 2 a_2 a2): a 1 ⊕ a 2 ⊕ s ⊕ a 3 . . . ⊕ a n = s ⊕ s = 0 a_1 \oplus a_2 \oplus s \oplus a_3 ... \oplus a_n = s \oplus s =0 a1⊕a2⊕s⊕a3...⊕an=s⊕s=0 ,则可以证明,一个必胜态一定可以转换得到一个必输态。
- 必输态的后继状态全部都是必胜态
如果是必输态,则上面的等式 s = 0 s = 0 s=0,则每个 a i a_i ai 的第k位一定都是偶数个,因此如果去掉一个1一定全部都是奇数,则存在 s ≠ 0 s \ne 0 s=0 ,则全部都是必胜态。
则我们根据上面的结论可以得到 A C AC AC 代码:
#include<bits/stdc++.h>
#if 0
#define int long long
#endif
const int N=1e4+10;
int n,a[N];
void solve(){
std::cin>>n;
for (int i=1;i<=n;i++){
std::cin>>a[i];
}
int sum=0;
for (int i=1;i<=n;i++){
sum^=a[i];
}
if (sum){
std::cout<<"Yes\n";
}
else{
std::cout<<"No\n";
}
}
signed main(){
int t;
std::cin>>t;
while (t--){
solve();
}
return 0;
}
方案
这道题目与上道题目基本一致,唯一的区别是这道题目还需要求出在先取必胜的前提下,第一次应该如何选?
- 必胜态的后继状态至少存在一个必输态。
因此如果甲先手赢,则乙一定处于一个必输态的状态。
因此可以考虑构造一个乙是必输态的情况,则就是甲先手赢的选择的情况。
由必胜态转换为必输态的,根据第一条定理:
则我们就是需要找出这样的一个状态: a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n ⊕ k = s ≠ 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n \oplus k = s \ne 0 a1⊕a2⊕a3...⊕an⊕k=s=0 ,
则只需要减少一个数字,使得 a i ⊕ k < a i a_i \oplus k <a_i ai⊕k<ai 即可。
同时找到之后,需要修改 a i a_i ai 位置的值。
#include<bits/stdc++.h>
#if 0
#define int long long
#endif
const int N=500010;
int n,a[N];
signed main(){
std::cin>>n;
int s=0;
for (int i=1;i<=n;i++){
std::cin>>a[i];
s^=a[i];
}
if (!s){
std::cout<<"lose\n";
return 0;
}
//赢的时候确定第一次取的方法
//需要减少一个数字,使其异或值之和s=0,则检查每个数字^s是否比本身小
for (int i=1;i<=n;i++){
if ((a[i]^s)<a[i]){
std::cout<<a[i]-(a[i]^s)<<" "<<i<<"\n";
a[i]^=s;
break;
}
}
for (int i=1;i<=n;i++){
std::cout<<a[i]<<" ";
}
return 0;
}
台阶型
给你n堆石子,并且把他们放在不同的台阶上,每次可以将第 k k k 个台阶上的石子移动一些到 k − 1 k-1 k−1 阶上去,直到最后台阶为0时,无法再移动,问能否先手必胜?
台阶型和上面的普通型其实是一个道理。
普通型:
- a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n \ne 0 a1⊕a2⊕a3...⊕an=0 : 先手必胜
台阶型:
- a 1 ⊕ a 3 ⊕ a 5 . . . ⊕ a n ≠ 0 a_1 \oplus a_3 \oplus a_5 ... \oplus a_n \ne 0 a1⊕a3⊕a5...⊕an=0 :先手必胜
为什么?
始终记住上面的两条定理:
- 必胜态一定可以转换为一个必输态
- 必输态不可能转换为另一个必输态,之能转换为必胜态。
因此我们就可以创造先手的必胜态,则对于后手的来说一定是必输态。
如果创造呢?同样是要缩小一个数字,即找到 a i ⊕ k a_i \oplus k ai⊕k ,则一定可以替换 a i a_i ai 的位置,则最后就可以把一个必胜态转变为必输态。
即转换为: a 1 ⊕ a 3 ⊕ a 5 . . . ⊕ a n ⊕ s = s ⊕ s = 0 a_1 \oplus a_3 \oplus a_5 ... \oplus a_n \oplus s =s \oplus s =0 a1⊕a3⊕a5...⊕an⊕s=s⊕s=0 ,则对于乙来说是必输的。
#include <iostream>
#include <algorithm>
#if 0
#define int long long
#endif
const int N=1010;
int n,a[N],b[N];
void solve(){
std::cin>>n;
for (int i=1;i<=n;i++){
std::cin>>a[i];
}
std::sort(a+1,a+1+n);
for (int i=n;i>=1;i--){
b[n-i+1]=a[i]-a[i-1]-1;
}
int s=0;
//a1^a3^a5!=0 谁先手谁赢
for (int i=1;i<=n;i+=2){
s^=b[i];
}
if (!s){
std::cout<<"Bob will win\n";
}
else{
std::cout<<"Georgia will win\n";
}
}
signed main(){
int t;
std::cin>>t;
while (t--){
solve();
}
return 0;
}