博弈SG函数

公平组合游戏ICG
若一个游戏满足:

  1. 由两名玩家交替行动;
  2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
  3. 不能行动的玩家判负;

则称该游戏为一个公平组合游戏。
NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。

我们任何一个游戏都可以看成一个图,一个状态机,图是个有向无环图,图中有一个唯一的起点,然后起点上会放一枚棋子,然后两名玩家交替着把棋子沿着边往后移动,每个玩家每次可以选择任意一枚棋子往后移动一格,不能移动的玩家就输了

有向图游戏
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

nim游戏
先手必败状态:走到的所有状态都是先手必胜
先手必胜状态:至少有一种情况拿完之后变成了先手必败状态

异或:所有数成对的话,异或值是0;但是异或值是0,不一定成对

对于这个题,有一个结论,就是当所有的数异或和是0的时候,就是先手必败,否则就是先手必胜

如果先手遇到的是异或和不是0的话,那么,他一定可以从某一堆中拿走一些石子,让它变成异或和是0的情况
我们可以证明一下
假设他们的异或和是x,然后假设x的二进制表示中最高位1在第k位,说明a1到an中必然至少存在一个数ai,它的第k位是1,显然有ai^x 然后剩余的异或起来,就是x^x = 0,就证明,先手可以把它变成必败状态

接下来证明,a1到an异或和等于0,无论怎么拿,剩余的异或和都不是0,假设我们从ai中拿了,然后剩下的是ai‘的话,用反证法,假设剩余的异或和也是0,那个ai和ai’就必须相等
博弈SG函数_第1张图片
得到以上等式,左右两边同时异或一下,然后大部分成对的,就消掉了,就剩下了ai ^ ai’ = 0,就得到了这俩相等

SG函数

SG函数
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。

Mex运算
设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:
mex(S) = min{x}, x属于自然数,且x不属于S

设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)

有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。

emmm,该怎么解释这玩意呢,大概就是,单独判断每个点的sg值,如果之前到过,就直接返回
,也就避免了指数级别上涨,也就是记搜的优化,时间复杂度就变成了1e6。

集合NIM游戏

#include 
#include 
#include 
#include 

using namespace std;

const int N = 110, M = 10010;

int n, m;
int s[N], f[M];//存储能取的集合,存储sg值


int sg(int x)
{
    if (f[x] != -1) return f[x];

    unordered_set<int> S;//每个点建一个set存储能到达的自然数
    for (int i = 0; i < m; i ++ )
    {
        int sum = s[i];
        if (x >= sum) S.insert(sg(x - sum));//插入能达的自然数的sg值
    }

    for (int i = 0; ; i ++ )
        if (!S.count(i))
            return f[x] = i;
}


int main()
{
    cin >> m;
    for (int i = 0; i < m; i ++ ) cin >> s[i];
    cin >> n;

    memset(f, -1, sizeof f);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }

    if (res) puts("Yes");
    else puts("No");

    return 0;
}

拆分
这还真是个奇奇怪怪的题目,主要是一开始没太看懂它的题意,它的意思就是,每次可以移除一堆石子,然后换成另外两堆规模小于它的石子

#include 
#include 
#include 

using namespace std;

const int N = 110;

int n;
int f[N];
unordered_set<int> S;

int sg(int x)
{
    if(f[x] != -1) return f[x];


    for(int i = 0 ; i < x ; i++)
        for(int j = 0 ; j <= i ; j++)//规定j不大于i,避免重复
            S.insert(sg(i) ^ sg(j));//相当于一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,
                                   //等于这些局面SG值的异或和

    for(int i = 0 ; ; i++)
        if(!S.count(i))
            return f[x] = i;
}

int main()
{
    memset(f , -1 , sizeof f);

    cin >> n;
    int res = 0;
    while(n--)
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }

    if(res) puts("Yes");
    else puts("No");
    return 0;
}

你可能感兴趣的:(#,算法,c++)