SG函数相关

真是一个巨大的坑 noip之前还是填一填吧

概念

P点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。
N点:必胜点,处于此情况下,双方操作均正确的情况下必胜。

性质

  • 所有终结点是 必败点 P 。(我们以此为基本前提进行推理,换句话说,我们以此为假设)
  • 从任何必胜点N 操作,至少有一种方式可以进入必败点 P。反过来说,能递推到任一必败点的点均为必胜点
  • 无论如何操作,必败点P 都只能进入 必胜点 N.

SG函数

首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x . 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
以下sg不符定义,只是用来推状态的

EX1. Good Luck in CET-4 Everybody!

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 14211 Accepted Submission(s): 9057
Problem Description
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:

1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
Input
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。
Output
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。
Sample Input
1
3
Sample Output
Kiki
Cici

用DP可以得出相关状态,0表示当前点未必败点,否则为必胜点

#include<cstdio>
const int N=1002;
int n,f[N],sg[N];
void getsg(int n){
    for(int i=1;i<=n;i++){
        sg[i]=0;
        for(int j=0;f[j]<=i;j++)if(!sg[i-f[j]]){sg[i]=1;break;}
    }
}
int main(){
    f[0]=1;for(int i=1;i<=15;i++)f[i]=f[i-1]<<1;
    getsg(1000);
    while(scanf("%d",&n)!=EOF){
        if(sg[n]!=0)puts("Kiki");else puts("Cici");
    }
    return 0;
}

可以AC了,但打表能找到规律的
对于3的倍数的数,无论如何操作,最后必定会到达3这点,因为每次只能减2的幂,那么显然这种情况下先手会败

然后又去看了hdu2147
打表

#include<cstdio>
const int N=2002;
int n,m,sg[N][N];
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void getsg(){
	for(int s=2;s<=4000;s++)
		for(int j,i=max(1,s-2000);i<=min(2000,s-1);i++){
			j=s-i;
			if(i-1>0&&j-1>0&&!sg[i-1][j-1]){sg[i][j]=1;continue;}
			if(i-1>0&&!sg[i-1][j]){sg[i][j]=1;continue;}
			if(j-1>0&&!sg[i][j-1]){sg[i][j]=1;continue;}
		}
}
int main(){
	freopen("sg.out","w",stdout);
	getsg();
	for(int i=1;i<=100;i++){
		for(int j=1;j<=100;j++)printf("%d ",sg[i][j]);
		puts("");
	}
	while(scanf("%d%d",&n,&m),n||m)if(sg[n][m])puts("Wonderful!");else puts("What a pity!");
	return 0;
}

找规律

#include<cstdio>
int n,m;
int main(){
	while(scanf("%d%d",&n,&m),n||m){
		if((n&1)&&(m&1))puts("What a pity!");else puts("Wonderful!");
	}
	return 0;
}

hdu1848类似

#include <stdio.h>
#include <string.h>
#define MAXN 1000 + 10
#define N 20
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    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;
        for(j = 0;;j++) if(!S[j]){
            SG[i] = j;
            break;
        }
    }
}
int main(){
    int n,m,k;
    f[0] = f[1] = 1;
    for(int i = 2; i <= 16; i++)
        f[i] = f[i-1] + f[i-2];
    getSG(1000);
    while(scanf("%d%d%d",&m,&n,&k),m||n||k){
        if(SG[n]^SG[m]^SG[k]) printf("Fibo\n");
        else printf("Nacci\n");
    }
    return 0;
}
posted @ 2018-11-07 14:06  lnyzo  阅读(120)  评论(0)    收藏  举报