hdu1536 S-Nim 博弈sg模板题 两种写法(递归+!递归)

本文深入探讨了一种基于集合S的博弈论游戏算法,通过定义集合S中的最小未出现数(mex)和Sprague-Grundy函数(sg),介绍了两种算法实现方式:递归和非递归方法。通过实例讲解了如何利用这些理论来判断先手玩家是否必胜。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LINK

题目描述

给定n堆石子以及一个由k个不同正整数构成的数字集合S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
样例
2 2 5
1
3 2 4 7
输出
W

定理1:对于集合S,mex(S)=mex({x1,x2…})=S中 没有出现的 最小非负整数
定理2.1:sg(n)=mex({sg(i1),sg(i2),sg(i3)…})。 n为结点;i1,i2,i3…是n的后继结点
定理2.2:sg(G)=sg(head). G是一个有向图,head是G的头结点。
定理3:sg(G1)sg(G2)sg(G3)sg(Gn)为n个有向图的异或和,对于n个有向图游戏,这个异或和就是它的答案(和nim类似,这个定理感兴趣可以去证明)

算法1:递归

C++ 代码
#include <bits/stdc++.h>
using namespace std;
int s[110], k, m, l, x, sg[11000], ans;
//这个题目的有向图可以看成一个树,叶子结点就是必败结点值为0。非叶子结点存放mex({子节点的值})。
int sg_dfs(int n)
{
    bool mex[110]; //对于每个结点n都开一个数组mex 记录他的后继结点
    memset(mex, 0, sizeof(mex));
    if (sg[n] != -1)
        return sg[n];
    if (n - s[0] < 0) //最小的都不可以取,这个n没有后继了,数组中标记为0.
        return sg[n] = 0;
    for (int i = 0; i < k && (n >= s[i]); i++)
        mex[sg_dfs(n - s[i])] = 1; //n的后继标记为1
    for (int i = 0;; i++)
        if (!mex[i]) //mex集合中!没!出现过的最小的非负整数
            return sg[n] = i;
}
int main()
{
    ios::sync_with_stdio(false);
    while (cin >> k, k)
    {
        memset(sg, -1, sizeof(sg));
        sg[0] = 0;
        for (int i = 0; i < k; i++)
            cin >> s[i]; //s集合的值(每一步可以取得数)
        sort(s, s + k);
        cin >> m;
        while (m--)
        {
            cin >> l; //l堆
            ans = 0;
            for (int i = 0; i < l; i++)
            {
                cin >> x;
                ans ^= sg_dfs(x); //将每个堆当成一个有向图 最终答案为sg(x)异或和
            }
            if (ans)
                cout << 'W';
            else
                cout << 'L';
        }
        cout << endl;
    }
}

算法2 非递归

道理一样,写法不同,先初始化了sg数组。

C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 10010;
int SG[N], k, m, x, l, ans;
int a[110];
//返回集合s中的最小非负整数
int mex(int s[], int n)
{
    if (n == 0)
        return 0; //没有后继
    sort(s, s + n);
    n = unique(s, s + n) - s;
    for (int i = 0;; i++)
        if (i >= n || s[i] != i)
            return i;
}
//eg:x的后继为x-a[0],x-a[1],,,,,x-a[n-1];
int sg(int x)
{
    int cnt = 0;
    int b[N];
    for (int i = 0; i < k; i++)
        if (x >= a[i])
            b[cnt++] = SG[x - a[i]];
    return mex(b, cnt);
}
int main()
{
    ios::sync_with_stdio(false);
    while (cin >> k, k)
    {
        memset(SG, 0, sizeof(SG));
        for (int i = 0; i < k; i++)
            cin >> a[i]; //s集合的值(每一步可以取得数)
        sort(a, a + k);
        for (int i = 1; i < N; i++)
            SG[i] = sg(i);
        cin >> m;
        while (m--)
        {
            cin >> l; //l堆
            ans = 0;
            for (int i = 0; i < l; i++)
            {
                cin >> x;
                ans ^= SG[x]; //将每个堆当成一个有向图 最终答案为sg(x)异或和
            }
            if (ans)
                cout << 'W';
            else
                cout << 'L';
        }
        cout << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值