博弈论(Nim游戏)

博弈论(Nim游戏)

文章首发于我的个人博客:欢迎大佬们来逛逛

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 a1a2a3...an=0 :则是一个必胜态
  • a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n = 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n = 0 a1a2a3...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 a1a2a3...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 ais 去替换 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 a1a2sa3...an=ss=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 a1a2a3...ank=s=0

则只需要减少一个数字,使得 a i ⊕ k < a i a_i \oplus k <a_i aik<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;
}

台阶型

1704 – Georgia and Bob

给你n堆石子,并且把他们放在不同的台阶上,每次可以将第 k k k 个台阶上的石子移动一些到 k − 1 k-1 k1 阶上去,直到最后台阶为0时,无法再移动,问能否先手必胜?

台阶型和上面的普通型其实是一个道理。

普通型:

  • a 1 ⊕ a 2 ⊕ a 3 . . . ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus a_3 ... \oplus a_n \ne 0 a1a2a3...an=0 : 先手必胜

台阶型:

  • a 1 ⊕ a 3 ⊕ a 5 . . . ⊕ a n ≠ 0 a_1 \oplus a_3 \oplus a_5 ... \oplus a_n \ne 0 a1a3a5...an=0 :先手必胜

为什么?

始终记住上面的两条定理:

  • 必胜态一定可以转换为一个必输态
  • 必输态不可能转换为另一个必输态,之能转换为必胜态。

因此我们就可以创造先手的必胜态,则对于后手的来说一定是必输态。

如果创造呢?同样是要缩小一个数字,即找到 a i ⊕ k a_i \oplus k aik ,则一定可以替换 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 a1a3a5...ans=ss=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;
} 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuleo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值