sg函数:问题 M: Little Sub and Johann

因为队里只有一个人搞博弈的sg,所以我也来学一下了

 

/*大佬请无视这段解释sg是个什么东西*/

 

sg解决的事类nim博弈问题

所谓的nim博弈一般就是:

有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,

如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

然后类nim就是在拿石子上做功夫,

(什么限定拿1到m啦,

( 限定拿规定的个数了

(比如说斐波那契数列、或者说下面这个题里的,只能拿(比自己小的并且和自己互质的数)或者是(自己)

 

nim博弈的解决办法是将所有数异或起来,如果是0,那么先手必败,否则先手必胜

而类nim博弈,是将所有数的sg值异或起来,也就是说可以将nim博弈看成类nim博弈sg[x] = x的特例

而我们要做的就是把这个sg表(一维数组)打出来

 

下面是打表代码,f数组是每一堆可以拿的个数(具体解释的话这里是、链接

const int maxn = 1e5+7,mod = 1e9+7;
//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//Hash[]:mex{}
int f[maxn],sg[maxn],Hash[maxn];
void initSG(int n){
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=n;i++){//当前堆中有i个石头
        memset(Hash,0,sizeof(Hash));
        for(int j=1;f[j]<=i;j++)
            Hash[sg[i-f[j]]]=1;
        for(int j=0;j<=n;j++)if(Hash[j]==0){//求mes{}中未出现的最小的非负整数
            sg[i]=j;
            break;
        }
    }
}

 

 

那么关于这个题

来源是:2019年杭州师范大学第十二届程序设计竞赛,然后upc补题场结束后说不会再开了,所以链接就不贴了)

题意:

类nim博弈,每一堆可以拿的个数是与当前堆中个数互质且小于当前堆中的个数,或者删除一个堆

(也就是当前堆全部拿走)

题解:sg打表找规律

 

sg打表代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+7,mod = 1e9+7;
//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//Hash[]:mex{}
int f[maxn],sg[maxn],Hash[maxn];
void initSG(int n){
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=n;i++){
        memset(Hash,0,sizeof(Hash));
        //for(j=1;f[j]<=i;j++)
        //    Hash[sg[i-f[j]]]=1;
        for(int j=1;j<=i;j++)if(__gcd(j,i) == 1 || j == i)
            Hash[sg[i-j]] = 1;
        for(int j=0;j<=n;j++)if(Hash[j]==0){//求mes{}中未出现的最小的非负整数
            sg[i]=j;
            break;
        }
    }
}
int main(){
    initSG(1000);
    for(int i=1;i<=30;i++)printf("%3d-->%3d\n",i,sg[i]);
	return 0;
}

 

一个显然的规律是

1、对于一个合数:sg[x] = sg[y](y是x的最小质因子)

2、对于一个质数:显然是sg[x] = sg[y]+1(y是小于x的最大质数)

 

一个欧拉筛,然后就解决了

 

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e6+7;
bool vis[maxn];
int prim[maxn],cnt,SG[maxn]={0,1},T,n,x;
void init(){
    for(int i=2;i<maxn;i++){
        if(!vis[i])prim[cnt++] = i,SG[i] = cnt+1;
        for(int j=0;j<cnt&&i*prim[j]<maxn;j++){
            vis[i*prim[j]] = 1,SG[i*prim[j]] = SG[prim[j]];
            if(i%prim[j] == 0)break;
        }
    }
}
char s[2][50] = {"Subconscious is our king!","Long live with King Johann!"};
int main(){
    init();
    for(scanf("%d",&T);T--;){
        scanf("%d",&n);
        int ans = 0;
        while(n--)
            scanf("%d",&x),ans ^= SG[x];
        printf("%s\n",s[ans?0:1]);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值