sg函数2023.5.31

SG函数是用于解决博弈论中公平组合游戏(Impartial Combinatorial Games,ICG)问题的一种方法。
一.
ICG:
有两名玩家
两名玩家轮流操作,在一个有限集合内任选一个进行操作,改变游戏当前局面
一个局面的合法操作,只取决于游戏局面本身且固定存在,与玩家次序或者任何其它因素无关
无法操作者,即操作集合为空,输掉游戏,另一方获胜
经典Nim游戏:
地上有n堆石子,每堆石子数量可能不同,两人轮流操作,每人每次可从任意一堆石子里取走任意多枚石子,
 可以取完,不能不取,无石子可取者输掉游戏,问是否存在先手必胜的策略。


二.
 sg(x):=mex(sg(y)|x->y),mex表示一个集合中未出现的最小自然数,sg(x)=0必败态,sg(x)!=0必胜态
 例子,单堆Nim游戏,归纳总结为sg(0)=0,sg(1)=1,sg(2)=2...sg(n)=n,石子数不为0都是必胜态。
 同理:对于双堆Nim游戏当n==m时,sg(n,m)=0;其余sg(n,m)!=0
Sprague - Grundy定理:
设x1,x2,x3...为ICG的状态则sg(x1+x2+x3...)=sg(x1)^(异或)sg(x2)^sg(x3)...
利用SG定理,把单一游戏的情况推广到多个子游戏组合的情况


例题(洛谷P2148)[SDOI2009]E& D
思路:先sg打表,把一堆石子拿掉,另一堆任意拆成两堆,等于说由状态(a,b)可以转移到{ (c,d),c + d = a或c + d = b }
 对于每一个a,所有c+d=a的(c, d)的SG值集合我们可以先用一个bitset存起来,这样当我们求(a, b)的SG值时我们直接将a和b
对应的两个集合并起来再求mex就好啦。于是得到了打表代码
找到规律后,得到sg公式(a,b)=sg(a/2,b-1/2+1)+1,异或一=每组子状态即可

#include
#include
#include
using namespace std;
const int N = 10, M = N + 1;//随便调大小
typedef bitset B;
B s[M];
int ans[M][M];
inline int mex(B b) {//mex表示一个集合中未出现的最小自然数,mex模板
    int i = 0;
    while (b[i])++i;
    return i;
}
int main() {
    int i, j, k;
    for (i = 2; i <= N; ++i)
        for (j = 1, k = i - 1; k; ++j, --k)
            s[i].set(ans[j][k] = mex(s[j] | s[k]));//枚举合并
    for (i = 0; i < N; ++i)printf("%3d", i); puts("");
    for (i = 1; i < N; ++i) {//输出矩阵
        printf("%2d:", i);
        for (j = 1;  j+i <= N; ++j)
            printf("%3d", ans[i][j]);
        puts("");
    }
    return 0;
}


 

#include 
using namespace std;
using ll = long long;
int sg(ll a, ll b){
    if (a % 2 && b % 2)
        return 0;
    else if (a % 2 == 0)
        return sg(a / 2, (b - 1) / 2 + 1) + 1;
    else
        return sg(b, a);
}
int main()
{
    ll t, n, x, y;
    cin >> t;
    while (t--){
        int res = 0;
        cin >> n;
        for (int i = 0; i < n / 2; ++i){
            cin >> x >> y;
            res ^= sg(x, y);
        }
        cout << (res ? "YES" : "NO") << endl;
    }
    return 0;
}

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