SG函数与例题

int sg[maxn];//sg[n] n表示每堆数量
int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
bool vis[maxn];
const int k;//k是集合s的大小 

void get_sg()
{
    int i,j;
    for(i=0;i<maxn;i++)
    {
        memset(vis,0,sizeof(vis));
        j=0;
        while(j<k&&s[j]<=i)
        {
          vis[sg[i-s[j]]]=1;
        j++;
        }
        for(j=0;j<maxn;j++)
        if(!vis[j])
        {
            sg[i]=j;
            break;
        }
    }
}

int main()
{
    ...    //将s数组初始化,如果s数组不是从小到大递增的话需要进行sort
    memset(sg,-1,sizeof(sg));
    get_sg();
    if(sg[n]==0) //先手必败
    else    //先手必胜

   //如果有多堆,则
   // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
   // if(num==0) 则先手必败
   // else    先手必胜
    ...
}

 

//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[110],sg[10010],n;
int SG_dfs(int x)
{
    int i;
    if(sg[x]!=-1)
        return sg[x];
    bool vis[110];
    memset(vis,0,sizeof(vis));
    for(i=0;i<n;i++)
    {
        if(x>=s[i])
        {
            SG_dfs(x-s[i]);
            vis[sg[x-s[i]]]=1;
        }
    }
    int e;
    for(i=0;;i++)
        if(!vis[i])
        {
            e=i;
            break;
        }
    return sg[x]=e;
}

 

Good Luck in CET-4 Everybody!

 HDU - 1847 

题意:n 张牌( 1 <= n <= 1000),两个人轮流取牌,只能取2的幂次张牌 (即:1,2,4,8,16...),最后抓完牌的人获胜

题解:一堆,s数组取值为 2 ^ i (0 <= i <= 11)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int maxn = 1e3 + 5;
const int mod = 998244353;

#define k 12
int sg[maxn];//sg[n] n表示每堆数量
int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
bool vis[maxn];


void get_sg() {
    int i, j;
    for (i = 0; i < maxn; i++) {
        memset(vis, 0, sizeof(vis));
        j = 0;
        while (j < k && s[j] <= i) {
            vis[sg[i - s[j]]] = 1;
            j++;
        }
        for (j = 0; j < maxn; j++)
            if (!vis[j]) {
                sg[i] = j;
                break;
            }
    }
}

int main()
{
    int n;
    for(int i = 0; i < k; i++)
        s[i] = int(pow(2,i));
    while(cin >> n) {
        memset(sg, -1, sizeof(sg));
        get_sg();
        if (sg[n] == 0) //先手必败
            puts("Cici");
        else
            puts("Kiki");//先手必胜
    }

    //如果有多堆,则
    // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
    // if(num==0) 则先手必败
    // else    先手必胜

}
View Code

 

 Northcott Game

 HDU - 1730 

题意:如图所示,游戏在一个n行m列(1 ≤ n ≤ 1000且2 ≤ m ≤ 100)的棋盘上进行,每行有一个黑子(黑方)和一个白子(白方)。执黑的一方先行,每次玩家可以移动己方的任何一枚棋子到同一行的任何一个空格上,当然这过程中不许越过该行的敌方棋子。双方轮流移动,直到某一方无法行动为止,移动最后一步的玩家获胜。

题解:每一行中加的距离可以看作是石子的堆数,因为每一个当一个人往两侧走的时候,另一个人可以往那个方向走直至距离一样,这个基础上它还可以再走,所以中间的距离只会减少不会增加,就可以看作是一个多堆石子的NIM游戏

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int maxn = 1e3 + 5;
const int mod = 998244353;

#define k 12
int sg[maxn];//sg[n] n表示每堆数量
int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
bool vis[maxn];


void get_sg() {
    int i, j;
    for (i = 0; i < maxn; i++) {
        memset(vis, 0, sizeof(vis));
        j = 0;
        while (j < k && s[j] <= i) {
            vis[sg[i - s[j]]] = 1;
            j++;
        }
        for (j = 0; j < maxn; j++)
            if (!vis[j]) {
                sg[i] = j;
                break;
            }
    }
}

int main()
{
    int n,m;
    while(cin >> n >> m) {
        int a,b;
        int flag = 0;
        while(n--){
            cin >> a >> b;
            flag ^= (abs(a - b) - 1);
        }
        if(flag == 0) puts("BAD LUCK!");
        else puts("I WIN!");
    }

    //如果有多堆,则
    // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
    // if(num==0) 则先手必败
    // else    先手必胜

}
View Code

 

 Fibonacci again and again

 HDU - 1848 

题意:3堆石子,每次只能取斐波那契数列中的个数,去完最后一个的人赢

题解:打表fibonacci后打SG表

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int maxn = 1e3 + 5;
const int mod = 998244353;

int sg[maxn];//sg[n] n表示每堆数量
int s[50];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
bool vis[maxn];
int k;//k是集合s的大小

void get_sg()
{
    int i,j;
    for(i=0;i<maxn;i++)
    {
        memset(vis,0,sizeof(vis));
        j=0;
        while(j<k&&s[j]<=i)
        {
            vis[sg[i-s[j]]]=1;
            j++;
        }
        for(j=0;j<maxn;j++)
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
    }
}

int main()
{
    s[0] = 1;
    s[1] = 2;
    for(int i = 2;i ; i++){
        s[i] =  s[i - 1] + s[i - 2];
        if(s[i] > 1000) {
            k = i;
            break;
        }
    }
//    debug(k);
    int m,n,p;
    while(cin >> m >> n >> p) {
        if(m == 0 && n == 0 && p == 0)
            break;
        memset(sg, -1, sizeof(sg));
        get_sg();
        int flag = sg[n] ^ sg[m] ^ sg[p];
        if(flag == 0) puts("Nacci");
        else puts("Fibo");

    }
    //如果有多堆,则
    // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
    // if(num==0) 则先手必败
    // else    先手必胜

}
View Code

 

 S-Nim

 HDU - 1536 

题意:给出k个可取数的集合,m个查询。查询每次是否是先后获胜。

题解:给定s数组,记得排序

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int MAXN = 1e4 + 5;
const int mod = 998244353;


int sg[MAXN];
int s[MAXN];
bool vis[MAXN];
int k;
void SG() {
    int i,j;
    for(i = 0 ; i < MAXN; i ++) {
        memset(vis,0,sizeof vis);
        j = 0;
        while(j < k && s[j] <= i) {
            vis[sg[i - s[j]]] = 1;
            j++;
        }
        for(j = 0; j < MAXN; j++)
            if(!vis[j]) {
                sg[i] = j;
                break;
            }
    }
}
int main()
{
    int n;
    while(scanf("%d",&n) && n) {
        memset(sg,-1,sizeof sg);
        for(int i = 0; i < n; i++)
            scanf("%d",&s[i]);
        k = n;
        sort(s,s + k);
        SG();
        int m;
        scanf("%d",&m);
        while(m--) {
            int l;
            scanf("%d",&l);
            int flag = 0;
            while(l--) {
                int tmp;
                scanf("%d",&tmp);
                flag ^= sg[tmp];
            }
            if(flag == 0) putchar('L');
            else putchar('W');
        }
        printf("\n");
    }
}
View Code

 

Game of Hyper Knights (二维图中dfs打表sg函数)

 LightOJ - 1315 

题意:给定一个棋盘,左上角为(0,0),棋盘中有多个骑士,每一个骑士只能按照图中的6种方式移动,两个人轮流移动棋盘中任意一个骑士,当轮到某一个人移动骑士时,棋盘中的骑士都已经不能移动了则判定为输,Alice先移动棋盘中的骑士,最后输出Alice和Bob谁赢谁输。

题解:典型的博弈SG函数,对每一个骑士的起始位置求SG值,然后将所有的SG值进行异或,如果其值为0,则先手必败,即Bob 获胜,否则先手必胜,Alice获胜。又由于这道题是二维的,因此每一个位置都是由x和y两个值来决定的,因此这道题无法使用打表的方式进行求SG值,需要时dfs的方式,SG初始化为-1即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int MAXN = 1e3 + 5;
const int mod = 998244353;

int s[6][2] = {{1,-2},{-1,-3},{-1,-2},{-2,-1},{-3,-1},{-2,1}};
int sg[MAXN][MAXN];

int SG_dfs(int x,int y) {
    if(sg[x][y] != -1)
        return sg[x][y];
    bool vis[MAXN];
    memset(vis,0,sizeof vis);
    for(int i = 0; i < 6; i++) {
        int nx = x + s[i][0];
        int ny = y + s[i][1];
        if(nx >= 0 && ny >= 0) {
            SG_dfs(nx,ny);
            vis[sg[nx][ny]] = 1;
        }
    }
    int e;
    for(int i = 0 ; ; i++) {
        if(!vis[i]) {
            e =  i;
            break;
        }
    }
    return sg[x][y] = e;
}
int main()
{
    int ca = 1;
    int t;
    scanf("%d",&t);
    memset(sg,-1,sizeof sg);
    while(t--) {
        int n;
        scanf("%d",&n);
        int flag = 0;
        while(n--)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            flag ^= SG_dfs(x,y);
        }
        printf("Case %d: %s\n",ca++,flag == 0 ? "Bob" : "Alice");
    }
}
View Code

 

F - Game of Cards

 Gym - 101128G 

题意:有n堆扑克牌,两个人轮流玩游戏,游戏规则:先选一堆扑克牌,然后拿走堆顶0-k张,剩余的堆顶那一张牌上是几就必须再拿走几张,当某一方无牌可拿或者剩余张数不够必须拿走的张数时则该方输。

题解:sg,s数组为0 - k,打sg表的时候还要把最少面的牌去掉

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<queue>
#include<set>
#include<string.h>
#include<vector>
#include<deque>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-4
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
typedef long long LL;
typedef long long ll;
const int MAXN = 1e3 + 5;
const int mod = 998244353;

int sg[MAXN];
int pile[MAXN];
bool vis[MAXN];
int k;

void SG(int m)
{
    memset(sg, 0, sizeof(sg));
    for (int i = 1; i <= m; i++)
    {
        memset(vis, false, sizeof(vis));
        int j = 0;
        while(j <= k && j <= i) {
            if(i == j){
                j++;
                continue;
            }
            int t = i - j - pile[i - j];
            if(t >= 0)
                vis[sg[t]] = 1;
            j++;
        }
        for(j =0 ; j <= m; j++ ) {
            if(!vis[j]) {
                sg[i] = j;
                break;
            }
        }
    }
}
int main()
{
    int n;
    cin >> n >> k;

    int flag = 0;
    while(n--)
    {
        int m;
        cin >> m;
        for(int i = 1; i <= m; i++)
            cin >> pile[i];
        SG(m);
        flag ^= sg[m];
    }
    if(flag == 0) puts("Bob will win.");
    else puts("Alice can win.");
}

/*
4 1
4
1 1 1 1
6
2 1 2 1 2 1
4
1 1 1 1
6
2 1 2 1 2 1


2 1
1 1
3 1 2 1

 */
View Code

 

posted @ 2019-08-18 12:14  千摆渡Qbd  阅读(504)  评论(0编辑  收藏  举报