SG函数总结+例题

SG函数
先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x ,SG函数的终态为 SG(x)=0,当且仅当 x 为必败点P时。

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

SG[0]=0,f[]={1,3,4},

x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

以此类推…

x 0 1 2 3 4 5 6 7 8…

SG[x] 0 1 0 1 2 3 2 0 1…

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合,记录mex中的值
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

例题

1.POJ - 2311 Cutting Game
Urej loves to play various types of dull games. He usually asks other people to play with him. He says that playing those games can show his extraordinary wit. Recently Urej takes a great interest in a new game, and Erif Nezorf becomes the victim. To get away from suffering playing such a dull game, Erif Nezorf requests your help. The game uses a rectangular paper that consists of WH grids. Two players cut the paper into two pieces of rectangular sections in turn. In each turn the player can cut either horizontally or vertically, keeping every grids unbroken. After N turns the paper will be broken into N+1 pieces, and in the later turn the players can choose any piece to cut. If one player cuts out a piece of paper with a single grid, he wins the game. If these two people are both quite clear, you should write a problem to tell whether the one who cut first can win or not.
Input
The input contains multiple test cases. Each test case contains only two integers W and H (2 <= W, H <= 200) in one line, which are the width and height of the original paper.
Output
For each test case, only one line should be printed. If the one who cut first can win the game, print “WIN”, otherwise, print “LOSE”.
Sample Input
2 2
3 2
4 2
Sample Output
LOSE
LOSE
WIN
题意:有一个矩形网格图,双方轮流对它进行裁剪,每次将它剪成两半,先剪出1
1的小格子的人算赢,问先手是否有必胜策略。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=2e2+5;
int sg[maxn][maxn];
bool vis[maxn];
int gao(int x,int y){
	memset(vis,false,sizeof(vis));
	for(int i=2;i<x-1;i++){
		int tmp=sg[i][y]^sg[x-i][y];
		vis[tmp]=true;
	}
	for(int i=2;i<y-1;i++){
		int tmp=sg[x][i]^sg[x][y-i];
		vis[tmp]=true;
	}
	for(int i=0;;i++){
		if(!vis[i])return sg[x][y]=i;
	}
}
int main(){
	int n,k;
	for(int i=2;i<maxn;i++){
		for(int j=2;j<maxn;j++){
			if(i==2&&j==2)sg[i][j]=0;
			else if(i==2&&j==3)sg[i][j]=0;
			else if(i==3&&j==2)sg[i][j]=0;
			else if(i==3&&j==3)sg[i][j]=0;
			else sg[i][j]=gao(i,j);
		}
	}
	while(~scanf("%d%d",&n,&k)){
		if(sg[n][k])printf("WIN\n");
		else printf("LOSE\n");
	}
	return 0;
}

2.Good Luck in CET-4 Everybody! HDU - 1847
链接
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。

Good luck in CET-4 everybody!
Input
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。
Output
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。
Sample Input
1
3
Sample Output
Kiki
Cici

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int x[12],sg[1050];
bool vis[1050];
void init(){
	for(int i=0;i<11;i++){
		x[i]=1<<i;
	}
	sg[0]=0;
	for(int i=1;i<1002;i++){
		memset(vis,0,sizeof(vis));
		for(int j=0;x[j]<=i;j++){
			vis[sg[i-x[j]]]=true;
		}
		for(int j=0;;j++){
			if(!vis[j]){
				sg[i]=j;
				break;
			}
		}
	}
	return ;
}
int main(){
	int n;
	init();
	while(~scanf("%d",&n)){
		if(sg[n])printf("Kiki\n");
		else printf("Cici\n");
	}
	return 0;
} 

3.Paint Chain HDU - 3980
链接
Aekdycoin and abcdxyzk are playing a game. They get a circle chain with some beads. Initially none of the beads is painted. They take turns to paint the chain. In Each turn one player must paint a unpainted beads. Whoever is unable to paint in his turn lose the game. Aekdycoin will take the first move.

Now, they thought this game is too simple, and they want to change some rules. In each turn one player must select a certain number of consecutive unpainted beads to paint. The other rules is The same as the original. Who will win under the rules ?You may assume that both of them are so clever.
Input
First line contains T, the number of test cases. Following T line contain 2 integer N, M, indicate the chain has N beads, and each turn one player must paint M consecutive beads. (1 <= N, M <= 1000)
Output
For each case, print "Case #idx: " first where idx is the case number start from 1, and the name of the winner.
Sample Input
2
3 1
4 2
Sample Output
Case #1: aekdycoin
Case #2: abcdxyzk
题意:n个点构成一个环,每次取m个,求谁先获胜。
因为先手是一个环,所以取了m个后环会断开,所以求的是后手的sg函数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>

#define ll long long

using namespace std;

const int maxn = 1010;
int sg[maxn];
bool vis[maxn];
int m;

int mex(int n)
{
    if(sg[n] != -1)return sg[n];
    if(n < m)return sg[n] = 0;
    memset(vis,0,sizeof(vis));
    for(int i = m;i <= n;i++)
        vis[mex(i-m)^mex(n-i)] = true;
    for(int i = 0;;i++)
    if(!vis[i])
    {
        sg[n] = i;
        break;
    }
    return sg[n];
}




int main(){

     int t, n;
     scanf("%d",&t);
     for(int i = 1; i <= t; i++) {
        scanf("%d%d",&n,&m);
        if(n < m) {
                printf("Case #%d: abcdxyzk\n",i);
                continue;
        }
        n -= m;
        memset(sg, 255, sizeof(sg));
        for(int j = 0; j <= n; j++) sg[j] = mex(j);
        if(sg[n] == 0) printf("Case #%d: aekdycoin\n",i);
        else printf("Case #%d: abcdxyzk\n",i);
     }
     return 0;
}

4.Fibonacci again and again HDU - 1848
任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;

假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
Input
输入数据包含多个测试用例,每个测试用例占一行,包含3个整数m,n,p(1<=m,n,p<=1000)。
m=n=p=0则表示输入结束。
Output
如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。
Sample Input
1 1 1
1 4 1
0 0 0
Sample Output
Fibo
Nacci
两种打sg函数的方法

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
 
int f[1100],sg[1100],b[1100];
 
int main()
{
    f[1] = 1,f[2] = 2;
    for(int i = 3; ; ++i)
    {
        f[i] = f[i-1] + f[i-2];
        if(f[i] >= 1000)
            break;
    }
    sg[0] = 0,sg[1] = 1;
    for(int i = 1; i <= 1000; ++i)
    {
        memset(b,0,sizeof(b));
        for(int j = 1; f[j] <= i; ++j)
            b[sg[i-f[j]]] = 1;
 
        for(int j = 0; j <= 1000; ++j)
        {
            if(!b[j])
            {
                sg[i] = j;
                break;
            }
        }
    }
 
    int N,M,P;
    while(cin >> N >> M >> P && (N||M||P))
    {
        if(sg[N]^sg[M]^sg[P])
            cout << "Fibo" << endl;
        else
            cout << "Nacci" << endl;
    }
 
    return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[20],sg[1005],n,m,p;
int solve(int x){
	if(sg[x]!=-1)return sg[x];
	int vis[2005];
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=15;i++){
		if(x<f[i])break;
		vis[solve(x-f[i])]=1;
	}
	for(int i=0;i<=2000;i++){
		if(!vis[i])
		return sg[x]=i;
	}
}
int main(){
	f[1]=1;
	f[2]=2;
	for(int i=3;i<=15;i++){
		f[i]=f[i-1]+f[i-2];
	}
	memset(sg,-1,sizeof(sg));
	sg[0]=0;
	while(~scanf("%d%d%d",&n,&m,&p)){
		if(n==0&&m==0&&p==0)break;
		if((solve(n)^solve(m)^solve(p))==0)printf("Nacci\n");
		else printf("Fibo\n");
	}
	return 0;
} 

SG函数其实是一种暴力搜索,其重点在于找到子状态,然后划分不同的后继局面,后继局面的SG值取mex,子游戏的SG值取异或和。还有一点需要注意的是初始化时需要给出最后必败态的SG值,此处需要注意我们只知道必败态的SG值为0,但必胜态的SG值由于与mex和异或有关,因此我们无法预先给出。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值