sg函数小结
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函数小结的更多相关文章
- Nim游戏与SG函数 ——博弈论小结
写这篇博客之前,花了许久时间来搞这个SG函数,倒是各路大神的论文看的多,却到底没几个看懂的.还好网上一些大牛博客还是性价比相当高的,多少理解了些,也自己通过做一些题加深了下了解. 既然是博弈,经典的N ...
- 博弈问题之SG函数博弈小结
SG函数: 给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移 动者判负.事实上,这个游戏可以认为是所有Impartial Combinatorial Ga ...
- SG函数 专题练习
[hdu1536][poj2960]S-Nim 题意 题意就是给出一个数组h,为每次可以取石子的数目. 然后给你n堆石子每堆si.求解先手能不能赢? 分析 根据\(h\)数组预处理出\(sg[i]\) ...
- POJ 2425 A Chess Game 博弈论 sg函数
http://poj.org/problem?id=2425 典型的sg函数,建图搜sg函数预处理之后直接求每次游戏的异或和.仍然是因为看不懂题目卡了好久. 这道题大概有两个坑, 1.是搜索的时候vi ...
- 博弈论(nim游戏,SG函数)
说到自己,就是个笑话.思考问题从不清晰,sg函数的问题证明方法就在眼前可却要弃掉.不过自己理解的也并不透彻,做题也不太行.耳边时不时会想起alf的:"行不行!" 基本的小概念 这里 ...
- HDU 5795 A Simple Nim 打表求SG函数的规律
A Simple Nim Problem Description Two players take turns picking candies from n heaps,the player wh ...
- 【转】博弈—SG函数
转自:http://chensmiles.blog.163.com/blog/static/12146399120104644141326/ http://blog.csdn.net/xiaofeng ...
- 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)运算 ...
- POJ2425 A Chess Game[博弈论 SG函数]
A Chess Game Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 3917 Accepted: 1596 Desc ...
随机推荐
- 各种jar包
springframework下载地址:http://maven.springframework.org/release/org/springframework/spring/ commons开头的j ...
- Iar8.1安装包破解
Iar8.1安装包链接链接:https://pan.baidu.com/s/1F6sxEcatk3_YPq47lvc8Mw 密码:mnlz 破解链接 https://www.cnblogs.com/ ...
- django之全局默认设置查看及admin语言设置
django之admin语言设置 admin后台管理默认使用的是英文,有时我们需要将其设置成自己的语言以方便使用管理: 将 LANGUAGE_CODE = '' 设置为欲设置的语言即可. 以下为dja ...
- 软件工程(FZU2015) 赛季得分榜,第三回合
SE_FZU目录:1 2 3 4 5 6 7 8 9 10 11 12 13 积分规则 积分制: 作业为10分制,练习为3分制:alpha30分: 团队项目分=团队得分+个人贡献分 个人贡献分: 个人 ...
- Shell脚本1
1Shell编程 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell脚本 Shell 脚本(shell scr ...
- 一个出色的表格(React实现__ES5语法)
本文主要是<React快速上手开发>一书中,第三章的内容代码整理,因为书中的代码零零散散,所以自己将整理了一下. 排序和编辑功能 <script> var header = [ ...
- httpd sshd firewalld 服务后面的d的意思
在操作系统中,一般系统的服务都是以后台进程的方式存在,而且都会常驻系统中,直到关机才结束.这类服务也称Daemon,在Linux系统中就包含许多的Daemon. 判断Daemon最简单的方法就是从名称 ...
- c++ 单引号"字符串" 用法
__int64 flag; //赋值超过4字节,编译错误 //flag = 'ABCDE'; //低于4字节,高位补 0 //flag = 'BCDE'; flag = 'A' << 24 ...
- day 7-20 视图,触发器,事务
一.视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...
- 2017年前小纪(有关http的一些缓存理论知识)
position的top和bottom的区别:前者基准点定在top,后者基准点定在bottom. for-in 遍历属性的顺序不确定 手机端,line-height对光标大小非常有影响 有些css3属 ...