【算法】博弈论

文章介绍了Nim游戏和S-Nim游戏的策略,重点在于先手是否必胜的判断。通过异或运算和SG函数,证明了当所有堆石子数量异或结果不等于0时,先手能将游戏状态转换为必败态,反之则必胜。对于S-Nim,引入了SG函数和mex运算,同样得出了类似结论。提供的C++代码示例用于判断游戏的胜负情况。
摘要由CSDN通过智能技术生成

【模板】nim 游戏【一本通提高博弈论】S-Nim为例。


Nim游戏

结论

如果

a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a n ≠ 0 a_{1}\oplus a_{2} \oplus a_{3}\oplus … \oplus a_{n}≠0 a1a2a3an=0

那么先手必胜。否则,先手必败。

证明

  • 我们考虑,如果在操作结束,什么也不能取的情况下,即:

    0 ⊕ 0 ⊕ 0 ⊕ … ⊕ 0 = 0 0\oplus 0 \oplus 0\oplus … \oplus 0 = 0 0000=0

    如果最后结果为 0 0 0 也就代表失败了。

  • 如果在操作中,当前为 a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a n ≠ 0 = x a_{1}\oplus a_{2} \oplus a_{3}\oplus … \oplus a_{n}≠0=x a1a2a3an=0=x,那么一定有一种方法使原式等于 0 0 0

    根据异或的性质,二进制第 k k k 为不同为 1 1 1,则 x x x 的最高位的 1 1 1 若在第 k k k 位上,一定有至少有一个 a i a_{i} ai 的第 k k k 位为 1 1 1

    这时, a i ⊕ x ≤ a i a_{i}\oplus x \le a_{i} aixai,可以从 a i a_{i} ai 中取出 a i − ( a i ⊕ x ) a_{i}-(a_{i}\oplus x) ai(aix) 个石子(根据前文石子个数必大于 0 0 0),那么此时堆中石子个数就为 a i − [ a i − ( a i ⊕ x ) ] = a i ⊕ x a_{i}-[a_{i}-(a_{i}\oplus x)]=a_{i}\oplus x ai[ai(aix)]=aix,将其带入原式进行操作,即:

    a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a i ⊕ x ⊕ … ⊕ a n = x ⊕ x a_{1}\oplus a_{2} \oplus a_{3}\oplus …\oplus a_{i} \oplus x\oplus… \oplus a_{n}=x\oplus x a1a2a3aixan=xx

    我们就可以得出,总有一种方法,是当前局面从不等于 0 0 0 变为 0 0 0

  • 如果在操作中,当前为 a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a n = 0 a_{1}\oplus a_{2} \oplus a_{3}\oplus … \oplus a_{n}=0 a1a2a3an=0,那么无论进行任何操作,都不能使原式等于 0 0 0

    考虑反证:

    a i ′ a_{i}' ai 为从任意一堆 a i a_{i} ai 中取出若干个石子后的个数,满足 0 ≤ a i ′ ≤ a i 0\le a_{i}'\le a_{i} 0aiai

    存在

    a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a i ⊕ … ⊕ a n = 0 a_{1}\oplus a_{2} \oplus a_{3}\oplus …\oplus a_{i} \oplus … \oplus a_{n}=0 a1a2a3aian=0

    a 1 ⊕ a 2 ⊕ a 3 ⊕ … ⊕ a i ′ ⊕ … ⊕ a n = 0 a_{1}\oplus a_{2} \oplus a_{3}\oplus …\oplus a_{i}' \oplus … \oplus a_{n}=0 a1a2a3aian=0

    将两式相互异或,得

    a i ⊕ a i ′ = 0 a_{i}\oplus a_{i}'=0 aiai=0

    因为 0 ≤ a i ′ < a i 0\le a_{i}'< a_{i} 0ai<ai,所以此等式不成立。则无论进行任何操作,都不能使原式等于 0 0 0

当还是初始状态时,如果不等于 0 0 0,先手一定可以将一个等于 0 0 0 的式子推给对方;而后手因为无论如何操作都不能使其等于 0 0 0,所以必会将一个不等于 0 0 0 的式子推回来。先手必胜。

如果等于 0 0 0,那先手无论如何操作都不能使其等于 0 0 0,所以必会将一个不等于 0 0 0 的式子推给对方;而后手这时就可以将等于 0 0 0 的式子推给我们。先手必败。

贴代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		int sum=0;
		for(int i=1;i<=n;i++)
		{
			int x;
			scanf("%d",&x);
			sum^=x;
		}
		if(!sum) puts("No");
		else puts("Yes");
	}
	return 0;
}

S-Nim

SG函数与mex运算

  • m e x mex mex 运算是将一个不存在与集合中的最小非负整数。

  • 我们可以将石子的数量变化转化成一张有向图的形式, S G SG SG 函数就是将 k k k 个状态 y i y_{i} yi S G SG SG 函数值取 m e x mex mex,即:

    m e x ( { S G ( y 1 ) , S G ( y 2 ) , S G ( y 3 ) … S G ( y k ) } ) mex(\left \{ SG(y_{1}),SG(y_{2}),SG(y_{3})…SG(y_{k})\right \} ) mex({SG(y1),SG(y2),SG(y3)SG(yk)})

    对于终点来说,因为无法再变化,所以为空集, S G SG SG 函数值即为 0 0 0

结论

如果

S G ( a 1 ) ⊕ S G ( a 2 ) ⊕ S G ( a 3 ) ⊕ … ⊕ S G ( a n ) ≠ 0 SG(a_{1})\oplus SG(a_{2}) \oplus SG(a_{3})\oplus … \oplus SG(a_{n})≠0 SG(a1)SG(a2)SG(a3)SG(an)=0

那么先手必胜。否则,先手必败。

证明

  • 如果在操作中,当前为 S G ( a 1 ) ⊕ S G ( a 2 ) ⊕ S G ( a 3 ) ⊕ … ⊕ S G ( a n ) ≠ 0 = x SG(a_{1})\oplus SG(a_{2}) \oplus SG(a_{3})\oplus … \oplus SG(a_{n})≠0=x SG(a1)SG(a2)SG(a3)SG(an)=0=x,那么一定有一种方法使原式等于 0 0 0

    根据异或的性质,两数二进制第 k k k 为不同为 1 1 1,则 x x x 的最高位的 1 1 1 若在第 k k k 位上,一定有至少有一个 S G ( a i ) SG(a_{i}) SG(ai) 的第 k k k 位为 1 1 1

    这时, S G ( a i ) ⊕ x ≤ S G ( a i ) SG(a_{i})\oplus x \le SG(a_{i}) SG(ai)xSG(ai),根据 S G SG SG 函数定义,可以取到 S G ( a i ) ⊕ x SG(a_{i})\oplus x SG(ai)x。可以从 a i a_{i} ai 中取出 S G ( a i ) − ( S G ( a i ) ⊕ x ) SG(a_{i})-(SG(a_{i})\oplus x) SG(ai)(SG(ai)x) 个石子(根据前文石子个数必大于 0 0 0),那么此时堆中石子个数就为 S G ( a i ) − [ S G ( a i ) − ( S G ( a i ) ⊕ x ) ] = a i ⊕ x SG(a_{i})-[SG(a_{i})-(SG(a_{i})\oplus x)]=a_{i}\oplus x SG(ai)[SG(ai)(SG(ai)x)]=aix,将其带入原式进行操作,即:

    S G ( a 1 ) ⊕ S G ( a 2 ) ⊕ S G ( a 3 ) ⊕ … ⊕ S G ( a i ) ⊕ x ⊕ … ⊕ S G ( a n ) = x ⊕ x SG(a_{1})\oplus SG(a_{2}) \oplus SG(a_{3})\oplus …\oplus SG(a_{i})\oplus x\oplus… \oplus SG(a_{n})=x\oplus x SG(a1)SG(a2)SG(a3)SG(ai)xSG(an)=xx

    我们就可以得出,总有一种方法,是当前局面从不等于 0 0 0 变为 0 0 0

  • 如果在操作中,当前为 S G ( a 1 ) ⊕ S G ( a 2 ) ⊕ S G ( a 3 ) ⊕ … ⊕ S G ( a n ) = 0 SG(a_{1})\oplus SG(a_{2}) \oplus SG(a_{3})\oplus … \oplus SG(a_{n})=0 SG(a1)SG(a2)SG(a3)SG(an)=0,那么无论进行任何操作,都不能使原式等于 0 0 0

    考虑反证:

    a i ′ a_{i}' ai 为从任意一堆 a i a_{i} ai 中取出若干个石子后的个数。

    S G ( a i ) SG(a_{i}) SG(ai) k k k,如果 S G ( a 1 ) ⊕ S G ( a 2 ) ⊕ S G ( a 3 ) ⊕ … ⊕ S G ( a i ′ ) ⊕ … ⊕ S G ( a n ) = 0 SG(a_{1})\oplus SG(a_{2}) \oplus SG(a_{3})\oplus …\oplus SG(a_{i}')\oplus… \oplus SG(a_{n})=0 SG(a1)SG(a2)SG(a3)SG(ai)SG(an)=0,那么 S G ( a i ′ ) SG(a_{i}') SG(ai) 仍然为 k k k,根据 S G SG SG 函数的基本性质, S G ( a i ′ ) SG(a_{i}') SG(ai),取不到 k k k,所以等式不成立,则无论进行任何操作,都不能使原式等于 0 0 0

当还是初始状态时,如果不等于 0 0 0,先手一定可以将一个等于 0 0 0 的式子推给对方;而后手因为无论如何操作都不能使其等于 0 0 0,所以必会将一个不等于 0 0 0 的式子推回来。先手必胜。

如果等于 0 0 0,那先手无论如何操作都不能使其等于 0 0 0,所以必会将一个不等于 0 0 0 的式子推给对方;而后手这时就可以将等于 0 0 0 的式子推给我们。先手必败。

贴代码

#include<bits/stdc++.h>
using namespace std;
int s[101];
int SG[10001];
bool vis[10001];
int k,n,m;
void SG_init()
{
    for(int i=1;i<=10001;i++)
    {
        memset(vis,0,sizeof(vis));
        for(int j=1;j<=k;j++)
        {
            if(i<s[j]) break;
            vis[SG[i-s[j]]]=1;
        }
        int cnt=0;
        while(vis[cnt])
            cnt++;
        SG[i]=cnt;
    }
    return ;
}
int main()
{
    while(scanf("%d",&k)&&k)
    {
        for(int i=1;i<=k;i++) scanf("%d",&s[i]);
        sort(s+1,s+k+1);
        SG_init();
        scanf("%d",&m);
        while(m--)
        {
            int sum=0;
            scanf("%d",&n);
            for(int i=1;i<=n;i++)
            {
                int x;
                scanf("%d",&x);
                sum^=SG[x];
            }
            if(sum) putchar('W');
            else putchar('L');
        }
        puts("");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值