友链

dreagonm

dummyyummy

fengxunling

BLUESKY007

神犇__stdcall

硕佬maomao9173

Luogu P2575 高手过招(博弈论)

睾♂手过招

题意描述:给定n个长度为20的,由0和1组成的序列。对于每个序列,都可以进行操作:将任意一个1移动到其右边的第一个0处。

每次可以选择任意一个序列中的任意一个1操作。不能操作者输。给定初始n个序列,求是否为必胜态。

题解:

SG函数万岁

SG定理:对于任意一个状态 $ x $ ,如果其SG函数值 $ SG(x)=0 $ 则 $ x $ 为必败态,否则为必胜态。

定义其SG函数值 $ SG(x) $ 为不属于其所有后继状态的SG函数值集合的最小非负整数。

即: $ SG(x)=mex(S) $ 其中S是x的 后继状态的SG函数值集合。

比如,若x有5个后继状态,其SG函数值分别为1,5,2,0,4,则 $ SG(x)=3 $

对于某个游戏和(如本题中n个序列),其SG函数为其子游戏(本题中每个序列)的SG函数的Nim(异或)和。

比如,若对于某个游戏和,其子游戏的SG函数值分别为1,5,2,0,4,则该游戏和的SG函数值为15204=2。

一些针对本题的技巧

由于只有20位,采用状压的方式保存、求每个状态的SG函数。

话说最近沉迷位运算无法自拔......

首先我们可以想到一个预处理:如果一个序列,右边全是一段连续的1,那么必不可能走下一步,其SG函数值为0。

于是便有:


 for(int i(0);i<=20;i++){
        SG[(1<<i)-1]=0;//(1<<i)-1,就是把0到i-1所有位都变为1
    }

然后是求SG函数的:利用SG函数的定义,求出所有子状态的SG函数,然后:

如果最小的函数值大于0,则取0;

否则,取第一个「空隙」的位置。

如果自始至终都没有「空隙」,那么就取最大一个函数值+1。

这一段位运算有点难懂,直接贴代码


int sg(int x){
    if(SG[x]!=-1)return SG[x];
    int cnt=0;
    int t=(x+1)-lb(x+1);//去掉末尾连续1后 
    int k=lb(t);
    while(k){
        t=t^k;//从t中抹掉k 
        int tmp=k;//找到从tmp往右数第一个空位 
        while((x^k)<x){//如果x换掉k位后小于x,则第k位为1,反之则为空位 
            k=k>>1;//找到从tmp往右数第一个空位 
        }
        //new:x^k^tmp
        cnt++;
        a[cnt]=sg(x^k^tmp);//记录所有x子状态的sg值。 
        k=lb(t);
    }
    sort(a+1,a+1+cnt);
    if(a[1]>0)return SG[x]=0;
    for(int i(1);i<=cnt;i++){
        if(a[i]-a[i-1]>1)return SG[x]=a[i-1]+1;
    }
    return SG[x]=a[cnt]+1;
}

posted @ 2018-09-03 08:03  soul_M  阅读(203)  评论(0编辑  收藏  举报