这里写目录标题
组合游戏
什么是组合游戏:
组合游戏需要有以下特点:
- 1.题目描述一般为A,B两人做游戏
- 2.AB交替进行某种游戏规则的操作,每次操作选手可以在有限的操作集合中选取一种。
- 3.游戏的任意一种局面的合法操作集合只与这个局面本身有关,不取决与其他因素,如选手,以前的操作
- 4.如果当前选手无法进行合法的操作时,该选手判负
N/P
- 必胜点N:处于该点的选手必胜。
- 必败点P:处于该点的选手必败
组合游戏有向图模型
任意一种组合游戏都能转化为一种有向图。组合游戏的每种状态都对应该有向图中的某个点。该图中没有出度的点为P点,每种操作如从一种状态转向另一种状态,则让这两个状态的点连一条有向边。则该组合游戏就变成成了在图中走点,判断谁会到P点的游戏。
SG函数与SG定理
初学sg函数
- 定义: s g [ x ] = m e x ( s g [ y 1 ] , s g [ y 2 ] , s g [ y 3 ] … … ) sg[x]=mex(sg[y1],sg[y2],sg[y3]……) sg[x]=mex(sg[y1],sg[y2],sg[y3]……),其中y1y2y3……为x的后继结点。
- mex运算: m e x ( S ) mex(S) mex(S) 表示集合 S S S 中没有出现过的最小非负整数。
- 结论: s g = 0 sg=0 sg=0 的点为必败点, s g ≠ 0 sg\not=0 sg=0 的点为必胜点。
- 个人理解: 如果 x x x 的后继结点中有 s g sg sg 值为0的结点,那么 s g [ x ] sg[x] sg[x] 的值就不会为 0, x x x 为必胜点,因为处于 x x x 状态的人可以走向后继结点 s g sg sg 值为 0 的点,使得对方面对必败点;如果 x x x 的后继结点中没有 s g sg sg 值为 0 的结点,那么 s g [ x ] sg[x] sg[x] 的值就会为 0, x x x 为必败点,因为处于 x x x 状态无论往哪个后继状态移动,都将令对方面对必胜点。
sg函数打表(求出所有状态的sg值)
int main() {
memset(sg,0,sizeof(sg));
for(int i=1; i<=n; i++) {
for(int j=0; j<=n; j++)vis[j]=0; //清0
for(/*后继状态*/) {
vis[sg[i-j]]=1;
}
for(int j=0; 1; j++) { //mex找到最小非负整数
if(vis[j]==0) {
sg[i]=j;
break;
}
}
}
}
SG定理
- 定理描述: 一个游戏的SG值等于该游戏包含的各个子游戏的 SG 值的异或和。ps:异或和的运算优先级较低,记得加括号防自闭。
SG 打表练习题
例题1:sg 打表模板
- 题目描述: 有 n 张牌,A 和 B 从 A 开始,依次抽取 k 张牌( k 需要等于 2 的次幂,1,2,4,8……),谁无法操作就输。在二者足够聪明下,谁赢?
- 问题分析: 使用 sg 函数,显然必败点 sg[0]=0,x的后继结点为 x − 2 j x-2^j x−2j ,sg 打表,最后判断 s g [ n ] sg[n] sg[n] 是否为 0 即可。
int main() {
int n;
memset(sg,0,sizeof(sg));
for(int i=1; i<=n; i++) {
for(int j=0; j<=n; j++)vis[j]=0; //清0
for(int j=1; j<=n; j=j<<1) { //(sg[y1],sg[y2]……)
if(i<j)break;
vis[sg[i-j]]=1;
}
for(int j=0; 1; j++) { //mex
if(vis[j]==0) {
sg[i]=j;
break;
}
}
}
if(sg[n]==0)cout<<"Cici"<<endl;
else cout<<"Kiki"<<endl;
}
例题2:SG打表+SG定理
- 题目描述:有堆石头,石头数量分别为m,n,p,A和B从A开始,依次抽取k个石头(k需要等于斐波那契数列,1,2,3,5,8……),谁无法操作就输。在二者足够聪明下,谁赢?
- 问题分析:使用 sg 函数,显然必败点 s g [ 0 ] = 0 sg[0]=0 sg[0]=0,x 的后继结点为 x − f [ j ] x-f[j] x−f[j],sg 打表。该 SG 包含三个子游戏,即单独的三堆石子分别玩,那么该游戏的 SG 值即为三个子游戏 SG 值的异或和。判断异或和的正负即可。
int main() {
f[1]=1;
f[2]=2;
for(int i=3; i<=20; i++)f[i]=f[i-1]+f[i-2];
memset(sg,0,sizeof(sg));
for(int i=1; i<=2000; i++) {
for(int j=0; j<=2000; j++)vis[j]=0; //清0
for(int j=1; j<=20; j++) { //(sg[y1],sg[y2]……)
if(i<f[j])break;
vis[sg[i-f[j]]]=1;
}
for(int j=0; 1; j++) { //mex
if(vis[j]==0) {
sg[i]=j;
break;
}
}
}
int m,n,p;
cin>>m>>n>>p;
if(m==0&&n==0&&p==0)return 0;
if((sg[m]^sg[n]^sg[p])!=0)cout<<"Fibo"<<endl;
else cout<<"Nacci"<<endl;
}
NIM博弈模型分析
模式一
- 模型描述: 一堆石子共 n 个,A 和 B 从 A 开始,依次取走 x 个石子, x ∈ [ 1 , m ] x\in[1,m] x∈[1,m]。谁赢?
- 模型分析:
- 使用 sg 打表可以 O ( n m ) O(nm) O(nm) 暴力解。
- 如果先手取走 x 个石子,后手可以选择 ( m + 1 − x ) (m+1-x) (m+1−x) 个石子。使得每轮都有 ( m + 1 ) (m+1) (m+1) 的消耗。那么若 n n n 为 m + 1 m+1 m+1 的倍数,则后手显然必胜;若 n n n 不为 m + 1 m+1 m+1 的倍数,则先手可先取走 n % ( m + 1 ) n\%(m+1) n%(m+1) 个石子,使得剩下的石子为 m + 1 m+1 m+1 的倍数,而此时先手成为了后手,即先手必胜。
- s g sg sg 打表发现 : g [ x ] = x % ( m + 1 ) g[x]=x\%(m+1) g[x]=x%(m+1),(记这个)
模型二
- 模型描述: 有 n 堆石子,每堆石子分别为 a i a_i ai ,A 和 B 从 A 开始,依次选择一堆石子,并取走该堆中的 x 个石子, x ∈ [ 1 , m ] x\in[1,m] x∈[1,m]。谁赢?
- 模型分析: n 堆石子可以看成 n 个子游戏即模式 1 。 s g sg sg 定理异或和求总游戏的 s g sg sg 值即可。
模式三
- 模型描述: 一堆石子共 n 个,A 和 B 从 A 开始,依次取走任意个(>0)。谁赢?
- 模型分析: 很显然,先手直接拿光即先手必胜。考虑分析 sg 函数,发现: s g [ x ] = x sg[x] = x sg[x]=x,符合模型一的结论
模型四
- 模型描述: 有 n 堆石子,每堆石子分别为 a i a_i ai,A 和 B 从 A 开始,依次选择一堆石子,并取走该堆中的任意个石子(>0)。谁赢?
- 模型分析: n 堆石子可以看出 n 个子游戏即模式三。 s g sg sg 定理异或和求总游戏的 s g sg sg 值即可。
模型五(前四个的统一)
- 模型描述: 有 n 堆石子,每堆石子分别为 a i a_i ai,A 和 B 从 A 开始,依次选择一堆石子,并取走该堆中的 x 个石子, x ∈ [ 1 , l i ] x\in[1,l_i] x∈[1,li] 。谁赢?
- 模型分析: n 堆石子可以看出 n 个子游戏即模式一。求出每个游戏的 s g sg sg 值,异或和求总游戏的 s g sg sg 即可。
练习题二:NIM深入
例题1:变形NIM游戏
- 题目描述: 0 − n 0-n 0−n 的一维数轴上放有 m 个石子,每个石子所在的位置分别为 x i x_i xi,每个位置可放多个石头。A 和 B 从 A 开始,依次选取一个石子,并将其任意放在其左边的任意一个位置,0 处的石子不能再移至更左边。谁无法操作就输。在二者足够聪明下,谁赢?
- 问题分析: 将一个石子往左边移动 x 步就相当于取石子游戏中拿走 x 个石子,可以移动任意步即取走任意个,即模型三,则 s g [ x i ] = x i sg[x_i] = x_i sg[xi]=xi。m 个石子对应 m 个子游戏,判断总游戏的 sg 值即可。
例题2:可行方案计数
- 模型描述: 有 n 堆石子,每堆石子分别为 a i a_i ai,A 和 B 从 A 开始,依次选择一堆石子,并取走该堆中的任意个石子(>0)。若 A 能赢,请输出 A 第一步可行的方案数;否则输出0。
- 模型分析: 求出总游戏的异或和 s g sg sg 值。再一次扫描每堆石子,可行方案等价于:可以取走 x 个石子使得剩下局面变为必败局面,即: s g ⊕ a i ⊕ ( a i − x ) = 0 < = > s g ⊕ a i = a i − x sg⊕a_i⊕(a_i-x)=0<=>sg⊕a_i=a_i-x sg⊕ai⊕(ai−x)=0<=>sg⊕ai=ai−x,我们只需要依次判断是否存在这样的 x 即可,也就是 s g ⊕ a i < a i sg⊕a_i<a_i sg⊕ai<ai
int main() {
int n,sum=0,ans=0;
for(int i=1; i<=n; i++) {
cin>>a[i];
sum=sum^a[i];
}
if(sum==0)cout<<0<<endl;
else {
for(int i=1; i<=n; i++) {
if((sum^a[i])<a[i])ans++;
}
cout<<ans<<endl;
}
}