博弈论-acwing893.集合-Nim游戏

补充知识

有向图游戏

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

这个题每一堆石子都可以看做成一个有向图

Mex运算

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

SG函数

在有向图游戏中,对于每个节点x,设从x除法共有k条有向边,分别到达结点\(y_1,y_2,...,y_k\),定义\(SG(x)\)为x的后继结点\(y_1,y_2,...,y_k\)\(SG()\)函数值构成的集合再执行\(mex(S)\)运算的结果,即:\(SG(x)=mex({SG(y_1),SG(y_2),...,SG(y_n)})\)

特别的,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即:\(SG(G) = SG(s)\)

SG定理(有向图游戏的和)

\(G_1,G_2,...,G_m\)是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏\(G_i\),并在\(G_i\)上行动一步。G被称为有向图游戏\(G_1,G_2,...,G_m\)的和。

有向图游戏的和SG函数在数值上等于它所包含的各个子游戏SG函数的异或和,即:\(SG(G) = SG(G_1)\bigoplus SG(G_2)\bigoplus ...\bigoplus SG(G_m)\)

存在结论:

对于n个图,如果\(SG(G_1)\bigoplus SG(G_2)\bigoplus ...\bigoplus SG(G_N) \neq 0\),则先手必胜,否则先手必败

acwing893.集合-Nim游戏

思路

\(h[i]\)表示的是每一堆石子有多少个,将每一个\(h[i]\)看做一张有向图

例如\(S=[2,5],h[1]=10\)
看h[1]这张有向图:

(ps:注意求SG函数需要mex()函数求,看其定义不要搞乱了)

#include<iostream>
#include<unordered_set>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100, M = 10010;

int n,m;
int f[M],s[N]; //f表示可能出现过的sg函数值,s表示可选正整数的集合

int sg(int x)
{
    if(f[x] != -1) return f[x]; // 如果算出来过sg(x)就直接返回f[x]
    
    unordered_set<int> S; // 用来存储该节点下一步的节点他们的sg函数值
    for(int i = 0; i < n; i ++)
    {
        int sum = s[i];
        if(x >= sum) S.insert(sg(x - sum)); // 如果存在下一步,将下一步节点sg函数值存入
    }
    
    // mex操作
    for(int i = 0;;i ++)
    {
        if(!S.count(i)) return f[x] = i; 
    }
    
}

int main()
{
    cin >> n ;
    for(int i = 0; i < n; i ++) cin >> s[i];
    
    cin >> m;
    memset(f,-1,sizeof(f));
    
    int res = 0;
    for(int i = 0; i < m; i ++)
    {
        int x; // x表示这一堆石子的数量
        cin >> x;
        res ^= sg(x);
    }
    
    if(res) puts("Yes");
    else puts("No");
    
    return 0;
}
posted @ 2022-09-21 21:34  r涤生  阅读(98)  评论(0编辑  收藏  举报