尼姆博弈与贪心改版尼姆博弈 沙堆最优策略

什么是尼姆博弈?(最优策略)

        尼姆博弈是一个两人博弈,2名玩家轮流从n堆物品中拿取一定数量(最小值1最大值全部)的物品,每次拿取时先选择某一堆,再从中拿取任意数量个物品,至少拿1个,至多将这一堆物品全部拿走,不能不拿。拿到最后一个物品的玩家获胜。

        这种题一般不要模拟不要模拟不要模拟(根本模拟不了),所以我们需要在写代码前用数学进行分析和优化。

        优化:最优策略是什么

        我们采用逆向思维来想n堆沙子的最后的几种情况。

        

        1、当只剩下一堆沙子时: 你的最佳选择是将所有石子全部拿走,你赢。

        2、当剩下两堆沙子时:假设现在有两堆石子且数量不相同,那么你的最佳选择是取走多的那堆石子中多出来的那几个,使得两堆石子数量相同,这样,不管另一个怎么取,你都可以在另一堆中和他取相同的个数,这样的局面你就是必胜。比如有两堆石子,第一堆有3个,第二堆有5个,这时候你要拿走第二堆的三个,然后两堆就都变成了3个,这时你的对手无论怎么操作,你都可以“学”他,比如他在第一堆拿走两个,你就在第二堆拿走两个,这样你就是稳赢的。同理,两堆沙子数量相同时先手必败。

        3、当剩下三堆沙子时 ,我们用(a,b,c)表示3堆沙子的局势,首 先(0,0,0)显然是奇异局势(这里不用管为什么叫奇异局势,往后面看即可)。无论谁面对奇异局势,都必然失败。第二种奇异局势是 (0,n,n),只要与对手拿走一样多的物品,最后都将导致(0,0,0)。仔细分析一下,(1,2,3)也是奇异局势,无论对手如何拿,接下来都可以变为(0,n,n)的情型。

        ……………………………………………………………………………………………………

博弈双方都采取最佳策略,那么必然会存在以下的情况

一个状态是必败状态当且仅当它的所有后继都是必胜状态 

((先手人的)必胜状态都不可能一步再走到(后手人的)必胜状态)
一个状态是必胜状态当且仅当它至少有一个后继是必败状态

((先手人的)必输状态都可以一步到达(先手人的)必胜状态)

(反证法举例可以证明  以上两个命题的条件或结果一旦更改为假命题)

        那么双方的决策就都是给对方留下必败态,那么先手人是否赢(是否给对方留下必败态)只取决于输入堆的分布。

这里我们正式引入尼姆博弈结论(想看怎么来的可以去搜以下数学分析-尼姆博弈

Bouton定理: 先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。即状态(x1, x2, x3, …, xn)为必胜状态当且仅当x1 xor x2 xor x3 xor … xor xn =0。这样的操作也称为Nim和(Nim Sum)

        而异或运算符号为^;

        所以我们只需要堆每堆的沙子数异或即可。

        例题1:                                                                               

        例题简要题干:两个玩家A、B,A先拿沙子,每次拿一堆沙子中的若干沙子(最小为1最大为全部),最后一个拿沙子的人赢。

输入        T (1≤T≤10^4)表示数据组数,n堆沙子(10^5)和每堆沙子的个数(10^4)

输出        获胜玩家

        代码如下。

#include<iostream>
using namespace std;
int main()
{
    int T;
    cin >> T;
    int n, a;
    while (T--)
    {
        cin >> n;
        int ans = 0;
        while(n--)
        {
            scanf("%d", &a);
            ans ^= a;
            if (ans) printf("A\n");
            else printf("B\n");
        }
    }
        return 0;
}

例题二(看似尼姆实则贪心)

        

        例题简要题干:两个玩家A和B,A先拿沙子。但是游戏按如下步骤进行:

  • 奇数轮由A指定一堆非空的沙子堆开始。然后 B从这堆黑灰中拿走至少一(至多全部)单位数量黑灰。
  • 偶数轮由B指定一堆非空的黑灰堆开始。然后A从这堆黑灰中拿走至少一(至多全部)单位数量黑灰。

输入        T (1≤T≤10^4)表示数据组数,n堆沙子(10^5)和每堆沙子的个数(10^4)

输出        获胜玩家        

        那么双方的决策会受到对方的制约,不符合尼姆博弈的规律,所以本题进行贪心。 

        再说一次,不要模拟不要模拟不要模拟(根本模拟不了),所以我们需要在写代码前用数学进行分析和优化。               

        优化一:对输入的数据(堆数)进行优化

       对于输入的数据,我们只需要开辟大小为2的int数组,分别储存沙子个数为1的沙堆数和沙子个数大于1的沙堆数。沙子个数大于1的沙堆数可以归并的证明如下。

        一堆沙子,其被取走的可能性如下:

                        1.A取走

                        2.A取走一部分,B全部取走

                        3.A取走一部分,B取走一部分,A全部取走(等效为A全部取走)(在最优化策略中是a全部取走,这情况不会出现)

                        4.……………………(等效为第二步)

所以我们只需要开辟大小为2的int类型num数组,分别计数沙子为1的堆数(num[1})和沙子不为1的堆数num[2],进行判断。

         优化二:输入的沙子数为1的堆可以两两相消

             因为沙子数为1的沙堆只有一次操作的空间,而最贪心的策略就是模仿对方的下法,所以对方一但指定一个沙子数为1的堆让我全拿,我就同样指定一个一个沙子数为1的堆让对方全拿,即最后沙子数为1的堆只剩1或0。

        在这个条件下演化num[2]的个数得到如下结果               

                                       

 所以贪心代码如下

#include<iostream>
using namespace std;
int main()
{
    int T;
    cin >> T;
    while (T--) {
        int n,a,sum[3]={0,0,0};
        scanf_s("%d", &n);
        while (n--)
        {
            scanf_s("%d", &a);
            if (a == 1)
            {
                sum[1]++;
            }
            else {
                sum[2]++;
            }
        }
        if (sum[2] == 0)
        {
            if (sum[1] % 2 == 1)
                cout << "A" << endl;
            else
                cout << "B" << endl;
        }
        else
        {
            if(sum[1]%2==1)
                cout << "B" << endl;
            else
                cout << "A" << endl;


        }
       
    }
    return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星星也倦了/

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

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

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

打赏作者

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

抵扣说明:

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

余额充值