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. 各种jar包

    springframework下载地址:http://maven.springframework.org/release/org/springframework/spring/ commons开头的j ...

  2. Iar8.1安装包破解

    Iar8.1安装包链接链接:https://pan.baidu.com/s/1F6sxEcatk3_YPq47lvc8Mw 密码:mnlz 破解链接  https://www.cnblogs.com/ ...

  3. django之全局默认设置查看及admin语言设置

    django之admin语言设置 admin后台管理默认使用的是英文,有时我们需要将其设置成自己的语言以方便使用管理: 将 LANGUAGE_CODE = '' 设置为欲设置的语言即可. 以下为dja ...

  4. 软件工程(FZU2015) 赛季得分榜,第三回合

    SE_FZU目录:1 2 3 4 5 6 7 8 9 10 11 12 13 积分规则 积分制: 作业为10分制,练习为3分制:alpha30分: 团队项目分=团队得分+个人贡献分 个人贡献分: 个人 ...

  5. Shell脚本1

    1Shell编程 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell脚本 Shell 脚本(shell scr ...

  6. 一个出色的表格(React实现__ES5语法)

    本文主要是<React快速上手开发>一书中,第三章的内容代码整理,因为书中的代码零零散散,所以自己将整理了一下. 排序和编辑功能 <script> var header = [ ...

  7. httpd sshd firewalld 服务后面的d的意思

    在操作系统中,一般系统的服务都是以后台进程的方式存在,而且都会常驻系统中,直到关机才结束.这类服务也称Daemon,在Linux系统中就包含许多的Daemon. 判断Daemon最简单的方法就是从名称 ...

  8. c++ 单引号"字符串" 用法

    __int64 flag; //赋值超过4字节,编译错误 //flag = 'ABCDE'; //低于4字节,高位补 0 //flag = 'BCDE'; flag = 'A' << 24 ...

  9. day 7-20 视图,触发器,事务

    一.视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...

  10. 2017年前小纪(有关http的一些缓存理论知识)

    position的top和bottom的区别:前者基准点定在top,后者基准点定在bottom. for-in 遍历属性的顺序不确定 手机端,line-height对光标大小非常有影响 有些css3属 ...