sg函数小结

sg函数是处理博弈问题的重要工具。

我们知道sg(x)=mex{sg(j)|x能到达状态j}

sg(x)=0时代表后手赢,否则先手赢。

对于一个问题,如果某些子问题是相互独立的,我们就可以用sg定理,总问题的sg等于各个子问题的异或和。

看几道题:

hdu1848 Fibonacci again and again

任何一个大学生对菲波那契数列(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”,每个实例的输出占一行。

一道暴力算sg函数的题

#include<bits/stdc++.h>
using namespace std;
#define REP(i,st,ed) for(register int i=st,i##end=ed;i<=i##end;++i)
#define DREP(i,st,ed) for(register int i=st,i##end=ed;i>=i##end;--i)
typedef long long ll;
inline int read(){
int x;
char c;
int f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0');
return x*f;
}
inline ll readll(){
ll x;
char c;
ll f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1ll)+(x<<3ll)+(c^'0');
return x*f;
}
const int maxn=1000+10;
int f[maxn],fib[maxn],tmp;
bool p[maxn];
int main(){
#ifndef ONLINE_JUDGE
freopen("sg.in","r",stdin);
freopen("sg.out","w",stdout);
#endif
int n,m,P;
fib[1]=1,fib[2]=2;
for(tmp=3;;++tmp){
fib[tmp]=fib[tmp-1]+fib[tmp-2];
if(fib[tmp]>1000) break;
}
--tmp;
REP(i,1,1000){
memset(p,0,sizeof(p));
REP(j,1,tmp){
if(i<fib[j]) break;
p[f[i-fib[j]]]=1;
}
REP(j,0,i)
if(!p[j]){
f[i]=j;
break;
}
}
while(scanf("%d%d%d",&n,&m,&P)!=EOF && (n || m || P)){
if(f[n]^f[m]^f[P]) printf("Fibo\n");
else printf("Nacci\n");
}
return 0;
}

bzoj1228: [SDOI2009]E&D

Description

小E 与小W 进行一项名为“E&D”游戏。游戏的规则如下:桌子上有2n 堆石子,编号为1..2n。其中,为了方便起见,我们将第2k-1 堆与第2k 堆(1 ≤ k ≤ n)视为同一组。第i堆的石子个数用一个正整数Si表示。一次分割操作指的是,从桌子上任取一堆石子,将其移走。然后分割它同一组的另一堆石子,从中取出若干个石子放在被移走的位置,组成新的一堆。操作完成后,所有堆的石子数必须保证大于0。显然,被分割的一堆的石子数至少要为2。两个人轮流进行分割操作。如果轮到某人进行操作时,所有堆的石子数均为1,则此时没有石子可以操作,判此人输掉比赛。小E 进行第一次分割。他想知道,是否存在某种策略使得他一定能战胜小W。因此,他求助于小F,也就是你,请你告诉他是否存在必胜策略。例如,假设初始时桌子上有4 堆石子,数量分别为1,2,3,1。小E可以选择移走第1堆,然后将第2堆分割(只能分出1 个石子)。接下来,小W 只能选择移走第4 堆,然后将第3 堆分割为1 和2。最后轮到小E,他只能移走后两堆中数量为1 的一堆,将另一堆分割为1 和1。这样,轮到小W 时,所有堆的数量均为1,则他输掉了比赛。故小E 存在必胜策略。

Input

的第一行是一个正整数T(T ≤ 20),表示测试数据数量。接下来有T组数据。对于每组数据,第一行是一个正整数N,表示桌子上共有N堆石子。其中,输入数据保证N是偶数。第二行有N个正整数S1..SN,分别表示每一堆的石子数。

Output

包含T 行。对于每组数据,如果小E 必胜,则输出一行”YES”,否则输出”NO”。

数据规模和约定

对于20%的数据,\(N = 2\);

对于另外20%的数据,\(,N ≤ 4,S_i ≤ 50\);

对于100%的数据,\(,N ≤ 2×10^4,S_i ≤ 2×10^9\)。

很显然每组之间是独立的,所以我们可以考虑每一组的sg函数。

设两堆石子分别是x,y

打个表,发现规律:

当x和y均为奇数时sg(x,y)=0;

否则sg(x,y)=sg((x+1)/2,(y+1)/2)+1

#include<bits/stdc++.h>
using namespace std;
#define REP(i,st,ed) for(register int i=st,i##end=ed;i<=i##end;++i)
#define DREP(i,st,ed) for(register int i=st,i##end=ed;i>=i##end;--i)
typedef long long ll;
inline int read(){
int x;
char c;
int f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0');
return x*f;
}
inline ll readll(){
ll x;
char c;
ll f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1ll)+(x<<3ll)+(c^'0');
return x*f;
}
int getsg(int x,int y){
if((x&1) && (y&1)) return 0;
return getsg((x+1)>>1,(y+1)>>1)+1;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("sg.in","r",stdin);
freopen("sg.out","w",stdout);
#endif
int T=read();
while(T--){
int n=read();
int sum=0;
REP(i,1,n>>1){
int x=read(),y=read();
sum^=getsg(x,y);
}
if(sum) printf("YES\n");
else printf("NO\n");
}
return 0;
}

ARC091 F - Strange Nim

题意:有n堆石子,每堆石子有\(a_i\)个,有一个数\(k_i\)现在两个人博弈,每个人可以拿掉一堆石子里\(1\)~\(\lfloor \frac {x} {k_i}\rfloor\)数量的石子,x为当前这一堆石子的数量,谁不能拿就输了,求谁赢。

数据范围:

\(1≤N≤200\)

\(1≤A_i,K_i≤10^9\)

又是找规律。。k=2的情况是石子游戏。规律也有些相似

通过打表发现

当x%k=0时\(sg(x)=x/k\)

\(sg(x)=sg(x-\lfloor \frac {x} {k} \rfloor + 1)\)

所以我们可以递归求sg函数,但是这样会T

我们\(\lfloor \frac {x} {k} \rfloor + 1\)相同的放在一次一起处理就可以了

#include<bits/stdc++.h>
using namespace std;
#define REP(i,st,ed) for(register int i=st,i##end=ed;i<=i##end;++i)
#define DREP(i,st,ed) for(register int i=st,i##end=ed;i>=i##end;--i)
typedef long long ll;
inline int read(){
int x;
char c;
int f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0');
return x*f;
}
inline ll readll(){
ll x;
char c;
ll f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1ll)+(x<<3ll)+(c^'0');
return x*f;
}
const int maxn=1e5+10;
int main(){
int n=read(),ans=0;
REP(i,1,n){
int x=read(),y=read();
while(x%y!=0){
int r=(x/y)*y;
x-=(x/y+1)*max((x-r)/(x/y+1),1);
if(x<y) break;
}
ans^=x/y;
}
if(ans) printf("Takahashi\n");
else printf("Aoki\n");
return 0;
}

1188: [HNOI2007]分裂游戏

Description

聪聪和睿睿最近迷上了一款叫做分裂的游戏。该游戏的规则试:共有n个瓶子,标号为0,1,2.....n-1,第i个瓶子中

装有p[i]颗巧克力豆,两个人轮流取豆子,每一轮每人选择3个瓶子。标号为i,j,k,并要保证i<j,j<=k且第i个瓶子

中至少要有1颗巧克力豆,随后这个人从第i个瓶子中拿走一颗豆子并在j,k中各放入一粒豆子(j可能等于k)。如

果轮到某人而他无法按规则取豆子,那么他将输掉比赛。胜利者可以拿走所有的巧克力豆!两人最后决定由聪聪先

取豆子,为了能够得到最终的巧克力豆,聪聪自然希望赢得比赛。他思考了一下,发现在有的情况下,先拿的人一

定有办法取胜,但是他不知道对于其他情况是否有必胜策略,更不知道第一步该如何取。他决定偷偷请教聪明的你

,希望你能告诉他,在给定每个瓶子中的最初豆子数后是否能让自己得到所有巧克力豆,他还希望你告诉他第一步

该如何取,并且为了必胜,第一步有多少种取法?

假定 \(1 < n < = 21,p[i] < = 10000\)

Input

输入文件第一行是一个整数t表示测试数据的组数,

接下来为t组测试数据(t<=10)。

每组测试数据的第一行是瓶子的个数n,

接下来的一行有n个由空格隔开的非负整数,表示每个瓶子中的豆子数。

Output

对于每组测试数据,输出包括两行,

第一行为用一个空格两两隔开的三个整数,表示要想赢得游戏,

第一步应该选取的3个瓶子的编号i,j,k,

如果有多组符合要求的解,那么输出字典序最小的一组。

如果无论如何都无法赢得游戏,那么输出用一个空格两两隔开的三个-1。

第二行表示要想确保赢得比赛,第一步有多少种不同的取法。

由于每一堆会影响后面的堆,所以不能把每一堆看成独立。

我们发现实际上问题只和每一堆的奇偶性有关。

所以我们可以把第i堆的一粒石子是独立的

这样我们就可以算sg函数了。

需要注意的是因为n不同所以预处理要反过来记录

求方案数直接枚举第一次的操作去算就行了。

#include<bits/stdc++.h>
using namespace std;
#define REP(i,st,ed) for(register int i=st,i##end=ed;i<=i##end;++i)
#define DREP(i,st,ed) for(register int i=st,i##end=ed;i>=i##end;--i)
typedef long long ll;
inline int read(){
int x;
char c;
int f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0');
return x*f;
}
inline ll readll(){
ll x;
char c;
ll f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1ll)+(x<<3ll)+(c^'0');
return x*f;
}
const int maxn=30;
int f[maxn],a[maxn];
bool p[10000+10];
void init(int n){
REP(i,1,n){
memset(p,0,sizeof(p));
REP(j,1,i-1)
REP(k,1,j)
p[f[j]^f[k]]=1;
REP(j,0,n*n) if(!p[j]){
f[i]=j;
break;
}
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("sg.in","r",stdin);
freopen("sg.out","w",stdout);
#endif
init(21);
int T=read();
while(T--){
int sum=0,n=read(),ans=0;
REP(i,1,n) a[i]=read();
REP(i,1,n) if(a[i]&1) sum^=f[n-i+1];
REP(i,1,n) if(a[i])
REP(j,i+1,n)
REP(k,j,n)
if((sum^f[n-i+1]^f[n-j+1]^f[n-k+1])==0){
if(!ans) printf("%d %d %d\n",i-1,j-1,k-1);
++ans;
}
if(!ans) printf("-1 -1 -1\n");
printf("%d\n",ans);
}
return 0;
}

4035: [HAOI2015]数组游戏

Description

有一个长度为N的数组,甲乙两人在上面进行这样一个游戏:首先,数组上有一些格子是白的,有一些是黑的。然

后两人轮流进行操作。每次操作选择一个白色的格子,假设它的下标为x。接着,选择一个大小在1~n/x之间的整数

k,然后将下标为x、2x、...、kx的格子都进行颜色翻转。不能操作的人输。现在甲(先手)有一些询问。每次他

会给你一个数组的初始状态,你要求出对于这种初始状态他是否有必胜策略。

Input

接下来2*K行,每两行表示一次询问。在这两行中,第一行一个正整数W,表示数组中有多少个格子是白色的,第二

行则有W个1~N之间的正整数,表示白色格子的对应下标。

Output

对于每个询问,若先手必胜输出"Yes",否则输出"No"。答案之间用换行隔开

数据范围

$,N<=1000000000 , K,W<=100 $, 不会有格子在同一次询问中多次出现。

考虑可以把问题转化为既可以选白点,又可以选黑点。因为如果选黑点,一定不能一次胜利,而反倒对对方有利,且对方可以选一样的将状态重置。

这样\(sg(x)=mex\{sg(2*x),sg(2*x)\) ^ \(sg(3*x) \cdots\}\)

这样时间复杂度还是不能过。

我们可以发现一个性质:

当 $ \lfloor \frac { n } { i } \rfloor $ = $ \lfloor \frac { n } { j } \rfloor $ 时 $ sg( i ) = sg( j ) $,可以用归纳法证明

之后我们就可以分块,相同的块放在一起。

记录答案时,大于\(\sqrt{n}\)的直接记录在$ \lfloor \frac {n} {i} \rfloor $上就可以了

#include<bits/stdc++.h>
using namespace std;
#define REP(i,st,ed) for(register int i=st,i##end=ed;i<=i##end;++i)
#define DREP(i,st,ed) for(register int i=st,i##end=ed;i>=i##end;--i)
typedef long long ll;
inline int read(){
int x;
char c;
int f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0');
return x*f;
}
inline ll readll(){
ll x;
char c;
ll f=1;
while((c=getchar())!='-' && (c<'0' || c>'9'));
if(c=='-') c=getchar(),f=-1;
x=c^'0';
while((c=getchar())>='0' && c<='9') x=(x<<1ll)+(x<<3ll)+(c^'0');
return x*f;
}
const int maxn=1e5+10;
int lim;
int n,f[maxn],g[maxn];
bool p[maxn];
int a[maxn];
inline int sg(int x){
if(x>lim) return f[n/x];
return g[x];
}
void init(){
for(int i=1,r;i<=n;i=r+1){
r=n/(n/i);int tmp=0,res=0;
for(int j=2,r1;j<=r;j=r1+1){
r1=r/(r/j);
int t=sg(r/j);tmp^=t;
p[tmp]=1;
a[++res]=tmp;
if((r1-j)&1) tmp^=t;
}
int t=1;
while(p[t]) ++t;
if(i>lim) f[n/i]=t;
else g[i]=t;
REP(i,1,res) p[a[i]]=0;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("sg.in","r",stdin);
freopen("sg.out","w",stdout);
#endif
n=read();
lim=(int)sqrt(n);
int T=read();
init();
// REP(i,1,n) cout<<i<<' '<<sg(n/i)<<endl;
while(T--){
int ans=0,m=read();
REP(i,1,m){
int x=read();x=n/x;
ans^=sg(x);
}
if(ans) printf("Yes\n");
else printf("No\n");
}
return 0;
}

sg函数小结的更多相关文章

  1. Nim游戏与SG函数 ——博弈论小结

    写这篇博客之前,花了许久时间来搞这个SG函数,倒是各路大神的论文看的多,却到底没几个看懂的.还好网上一些大牛博客还是性价比相当高的,多少理解了些,也自己通过做一些题加深了下了解. 既然是博弈,经典的N ...

  2. 博弈问题之SG函数博弈小结

    SG函数: 给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移 动者判负.事实上,这个游戏可以认为是所有Impartial Combinatorial Ga ...

  3. SG函数 专题练习

    [hdu1536][poj2960]S-Nim 题意 题意就是给出一个数组h,为每次可以取石子的数目. 然后给你n堆石子每堆si.求解先手能不能赢? 分析 根据\(h\)数组预处理出\(sg[i]\) ...

  4. POJ 2425 A Chess Game 博弈论 sg函数

    http://poj.org/problem?id=2425 典型的sg函数,建图搜sg函数预处理之后直接求每次游戏的异或和.仍然是因为看不懂题目卡了好久. 这道题大概有两个坑, 1.是搜索的时候vi ...

  5. 博弈论(nim游戏,SG函数)

    说到自己,就是个笑话.思考问题从不清晰,sg函数的问题证明方法就在眼前可却要弃掉.不过自己理解的也并不透彻,做题也不太行.耳边时不时会想起alf的:"行不行!" 基本的小概念 这里 ...

  6. HDU 5795 A Simple Nim 打表求SG函数的规律

    A Simple Nim Problem Description   Two players take turns picking candies from n heaps,the player wh ...

  7. 【转】博弈—SG函数

    转自:http://chensmiles.blog.163.com/blog/static/12146399120104644141326/ http://blog.csdn.net/xiaofeng ...

  8. HDU 1848 Fibonacci again and again【SG函数】

    对于Nim博弈,任何奇异局势(a,b,c)都有a^b^c=0. 延伸: 任何奇异局势(a1, a2,… an)都满足 a1^a2^…^an=0 首先定义mex(minimal excludant)运算 ...

  9. POJ2425 A Chess Game[博弈论 SG函数]

    A Chess Game Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 3917   Accepted: 1596 Desc ...

随机推荐

  1. duxing201606的原味鸡树

    链接 [http://murphyc.fun/problem/4011] 题意 描述 众所周知,duxing哥非常喜欢原味鸡.众所周知,原味鸡是长在原味鸡树上的. duxing哥因为是水产巨子,所以就 ...

  2. c++入门之类继承初步

    继承是面向对象的一种很重要的特性,先来复习基类的基本知识: 先上一段代码: # ifndef TABLE00_H # define TABLE00_H # include "string&q ...

  3. 解决linux用户切换失败 su:execute /usr/bin 没有权限

    问题描述: 回宿舍前,在root用户中安装fish,并修改其shell为fish.回宿舍之后,在图形界面用root用户进行登陆,莫名其妙登陆失败.没有任何提示信息,直接回到登陆界面.用非root用户登 ...

  4. java 8 jvm 内存配置

    jdk8内存参数解析与修改(新的参数) - LikeTech - CSDN博客https://blog.csdn.net/lk7688535/article/details/51767333 Java ...

  5. MyEclipse10 复制之前的项目部署到tomcat时项目名称对不上,还是复制前的项目名称,哪里修改设置

    工程 -- 右键属性 -- Myeclispse -- web修改一下发布名字就可以了.

  6. Baby-Step-Giant-Step 很酷的算法

    Baby-Step-Giant-Step BSGS算法用于解决形如:      A  ^  x  ≡  B  (  mod  C  ) 的问题.  学这个算法前需要具备以下知识:快速幂取模.扩展欧几里 ...

  7. Day 4-2 random模块

    import random random.randint(1,100) # 从1到100中随机取出一个数.包含100 random.randrange(1,100) #功能和上面一样.只是不包含100 ...

  8. 剑指offer(8)

    题目: 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 思路: 第一反应想到的是把数右移,每一位与1相与,然后判断个数,但是若输入的为负数,会出现死循环现象. 所以我们设置一个标志量 ...

  9. j收集ava面试题

    史上最全Java面试题(带全部答案) https://blog.csdn.net/linzhiqiang0316/article/details/80473906

  10. Kettle 变量(arg位置参数)

    1.表输入中使用?占位作为kettle转换变量 数据预览: 获取变量数据: 使用?传入变量 需要勾选替换sql语句中的变量,并选则从步骤插入数据中所在步骤 数据预览