(组合博弈)(sg函数模版)HDU 5724 Chess

大一暑期的时候接触过一点组合博弈,一年之后也基本忘光了,前两天从新找了不少资料温习了一遍。记录下我对组合博弈(sg函数)的理解:

一)sg函数

sg [x] = mex{ y | y是 x 的所有的可能直接后继} 

或:

sg [u] = mex{ v | 图中有一条u指向v的边}。

mex{}是定义在整数集合上的操作。它的自变量是任意整数集合,函数值是不属于该集合的最小自然数。 

很多资料上都只是写:sg [x] = mex{ y | y是 x 的一个后继},刚学的时候我总怀疑 y是x的直接后继,还是y是x的所有可能后继?,实际上应该是后者,y准确的来说也应该被描述为一个集合:{ y | y是x所有的可能直接后继}。我没有直接在论文或博客中看到这句话,但按照我的推理应该是这样的。

比如:

我们都知道,有n堆石子a1, a2, a3....an,每次都可以拿任意个,只能从一堆里拿,不能不拿。这个游戏sg值就等于ai。即第i队石子的sg[ i ]=ai。

按照 sg [x] = mex{ y | y是 x 的所有的可能直接后继},因为每次都可以从第 i 堆里拿任意个石子,所以可以将ai变成 0,1,2,3.....ai-1 任意一个数字。即集合 y={0,1,2......ai-1}所以 sg[ ai ]=a[ i ]。

再看到我大一时留下的sg函数模版:

<span style="font-size:12px;">void sprague_grundy()  
{  
    int i,j;  
    sg[0]=0;  
    for (i=1;i<H;i++){      //H为石头的堆数,k为s集合元素的个数
        memset(mex,0,sizeof(mex));  
        j=1;  
        while (j<=k && i>=s[j]){   
            mex[sg[i-s[j]]]=1;   //s[]为题目要求的只能取的个数
            j++;  
        }  
        j=0;  
        while (mex[j]) j++;  
        sg[i]=j;  
    }  
}  </span>
while循环里显然是把a[i]的所有可能后继都标记了。sg [x] = mex{ y | y是 x 的所有的可能直接后继},应该是无疑的了。


注:初学的同学maybe 会对 “y 是 x 的所有的可能直接后继”仍能不理解。


如图:

y1,y2,y3 是 x 的所有直接后继,z 是y2的直接后继,但是z不是x的直接后继

二)组合

一般定义组合游戏:
1、有且仅有两个玩家    2、游戏双方轮流操作    3、游戏操作状态是个有限的集合(比如:取石子游戏,石子是有限的,棋盘中的棋盘大小的有限的)  4、游戏必须在有限次内结束  5、当一方无法操作时,游戏结束。

还是石子游戏,如果只有一堆,显然,谁先手谁赢。如果N堆石子(就是N堆的组合),其实也很简单:
只要把n堆石子的sg值异或起来,最后结果若为0,则先手必输;弱不为0,则先手必赢。

看似神奇,和不想干的异或扯上了关系!但其实证明也很简单。网上资料很多,我就不再多说啥了。了解到这里,做一般的sg题目也够了,再复杂变形就要看造化了!不过其实网上资料(论文不仅限于论文)也很多。

总结一下:
对于简单的组合博弈,对于每堆“石子”,遍历其所有直接后继状态,并加入mex数组,求得sg[i]。最后再将“每堆石子”的sg值异或起来就是最后的结果。

说了这么多,再做HDU 5724 Chess应该没什么问题了!本来只是想写一篇HDU5724的题解,没想到写了这么多*~~~~*
【代码】
<span style="font-size:12px;">/* ***********************************************
Author        :angon
************************************************ */
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define REP(i,k,n) for(int i=k;i<n;i++)
#define REPP(i,k,n) for(int i=k;i<=n;i++)
#define scan(d) scanf("%d",&d)
#define scann(n,m) scanf("%d%d",&n,&m)
#define mst(a,k)  memset(a,k,sizeof(a));
#define LL long long
#define maxn 100005
#define mod 100000007
using namespace std;
int sg[(1<<20)+10];
void init()
{
    for(int i=1; i<(1<<20); i++)  //枚举一行棋盘的所有状态
    {
        int mex[25];
        memset(mex,-1,sizeof(mex)); //每个新状态mex函数都不一样
        int last=-1;
        for(int j=0; j<20; j++) //向右走,状态i的所有可能后继状态给它标记了
        {
            if(!(i&(1<<j))) //找到右边第一个为空的格子
                last=j;
            if((i&(1<<j))) //找到第一个棋子
            {
                if(last!=-1){
                    mex[sg[i^(1<<j)^(1<<last)]]=1;  //标记移动后的后继状态
                }
            }
        }
        int j=0;
        while(mex[j]!=-1) j++;
        sg[i]=j;
    }
}
int main()
{
    init();
    int t,n,m,x;
    scan(t);
    while(t--)
    {
        scan(n);
        int ans=0;
        for(int i=1; i<=n; i++)
        {
            scan(m);
            int t=0;
            for(int i=1; i<=m; i++)
            {
                scan(x);
                t^=1<<(20-x);
            }
            ans+=sg[t];
        }
        if(ans)
        {
            puts("YES");
        }
        else
        {
            puts("NO");
        }
    }
}</span><span style="font-size:14px;">
</span>



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值