从最基本的开始说起吧
博弈问题有很多种类,其中最常见的是公平组合游戏——也叫作ICG游戏(Impartial Combinatorial Games)。在ICG游戏里面,两个玩家轮流作出决策,并且每个人的决策集合是固定且相同的,游戏总会在有限步数内结束,没有平局。
解决博弈问题最基本的理论就是NP态理论。P态对应先手必败态,N态对应先手必胜态。我们很容易可以得到这样的判断规则:
- 如果一个状态有一种转移可以达到P态,那么它就是N态。
- 如果一个状态的任何转移都只能达到N态,那么它是P态。
原理很简单,因为每个人都是按照对自己最有利的策略进行的,所以对于先手来说,如果当前的状态有一种转移可以让对手面临先手必败态,那么他就一定会按照这个转移走;但是如果他没有办法让对手面临先手必败态,那么他就只能把必胜态让给对手,于是他就败了。
然而如果只用这个定理是没有办法解决大部分博弈问题的,因为可能问题是很多单一游戏的组合,玩家可以选择任意一个游戏进行操作——比如经典的Nim游戏。这个时候没有办法一下子判断是N态还是P态,也就需要用别的方法来解决问题。
博弈问题的万能手段——SG函数
当遇到多个单一游戏组合的问题的时候,能不能通过每个单一游戏的胜败状态来推出整个游戏的胜负呢?如果我们将规则定义为两人轮流操作,不能操作的人输,那么解决这一类问题就有一个几乎是万能的方法——SG函数。它的出现实际上是基于上面的NP态理论的,也就是说它的性质和NP态理论是相符合的。
于是我们给出SG函数的如下定义:
- 终止状态的SG值为0
- SG[u]=mex{SG[v]|v是u的后继状态}
- SG值为0的状态为P态,否则为N态
其中mex函数定义为集合中没有出现过的最小自然数,也就是
ICG游戏有一个十分重要的性质——如果把每个状态看做平面上的点,每个状态向它的后继状态连边,那么形成的将是一个有向无环图。有向图这个显然,无环这一点非常重要,因为对于ICG游戏来说是不可能走一圈又回到原来的状态的,这样游戏就可能无限进行下去了。这样的话我们就可以通过DP或者记忆化搜索的方法来求解每个状态的SG函数了。
但是这样定义这个SG函数为什么是对的呢?首先终止状态一定是先手必败态,因为终止状态没有后继,也就是面临这个状态的人不能操作了。这里就看出了上面要求不能操作的人输的必要性。对于其它状态来说,如果它的SG值为0,那么它的所有后继状态的SG值一定是大于0的,也就是它能转移到的所有状态都是必胜态,那么它就是必败态;如果它的SG值大于0,那么它的后继状态的SG值中一定出现了0,也就是它至少有一种方法能转移到必败态,于是它就是必胜态。这个证明思路类似于归纳法。
有了SG函数以后我们就可以解决多个单一游戏组合的问题了。对于由多个单一游戏组合成的游戏,有一个重要的定理就是
这个东西为什么是对的呢?我们分三种情况来证明:
- 当所有单一游戏的SG值都为0的时候,先手无论玩哪个游戏都是在必败态上进行,那么总的SG值显然是0。
- 当总的SG值不为0的时候,我们需要证明至少存在一种方案转移到总的SG值为0的方案。设当前SG值为k,每个单一游戏的SG值设为 SGi 。考察k的二进制表示中最高位的那个1,一定有某个 SGi 的这一位也是1,不然就无法异或得到k了。对于这个 SGi ,根据SG函数的求法——mex函数的定义可以得到状态i的后继一定包含了所有SG值小于 SGi 的状态。总的SG值去掉 SGi 以后的值是 SGi⊕k ,因为这个值一定会比 SGi 少掉最高位那个1,所以这个值也一定比 SGi 要小。那么只要把 SGi 转移到值为 SGki 的这个状态,就一定可以让最后总的异或值为0。
- 当总的SG值为0的时候,我们需要证明所有情况转移到的SG值都不为0。这里使用反证法,如果存在令 SGi→SG′i 的方法令最后的总SG值为0,那么为了不改变最后的异或值,每一位的1的个数奇偶性都不能变。而因为我们只变了一个数,如果这个过程里某一位从0变成了1或从1变成了0,那么这一位总的1的个数奇偶性一定会改变,也就是最后的异或结果一定不是0。
有了这个定理以后我们就可以用SG函数很方便的解决很多简单的博弈问题了。很多时候用记忆化搜索就能很快求出所有状态的SG函数。
几个例题
- POJ2975 Nim
尼姆游戏就是给出很多堆石子,两名玩家轮流操作,每次每个玩家可以从任意一堆中拿走任意多的石子,最后没有石子可拿的人输。对于只有一堆石子的情况来说,如果当前石子数为0,那么SG值显然是0;对于剩下的情况都可以以此为基础推导。推出SG值以后我们可以得到一个结论:对于数量为k的一堆石子,它的SG值为k。因为数量为k的石子可以转移到0..k-1的任何状态,用归纳法即可证明。那么把每一堆石子数异或起来就是总的SG值。 - POJ2960 S-Nim
- POJ2425 A Chess Game
上面两个题都是直接用题目给出的规则求SG函数就可以了。
Nim游戏的各种奇怪变形
规则反过来——Anti-Nim
上面所说的SG函数的一个重要前提就是当一个玩家没有操作的时候他输,但如果当这个人没有操作的时候判赢,就不能简单地用SG函数来求解了。虽然NP态理论还是成立的,但SG函数证明正确性的大前提没有了。
2009年贾志豪的论文《组合游戏略述——浅谈SG游戏的若干拓展及变形》中给出了对于这类Anti-SG游戏的一个通用定理——SJ定理:
对于任意一个 Anti-SG 游戏,如果我们规定当局面中所有的单一游
戏的 SG 值为 0 时,游戏结束,则先手必胜当且仅当: (1)游戏的 SG 函
数不为 0 且游戏中某个单一游戏的 SG 函数大于 1;(2)游戏的 SG 函数
为 0且游戏中没有单一游戏的 SG 函数大于 1。
有了这个定理,所有Anti_SG游戏就可以直接求出SG函数然后用上面的定理判断了。求SG函数的过程和普通的博弈题的做法是一样的。
几个例题(其实才两个)
- HDU1907 BZOJ1022 John
Anti-Nim游戏的板子。规则和Nim游戏一样只是取完最后一个石子的人输。因为Nim游戏的SG值就是这一堆的石子数目,所以不需要用记忆化搜索求SG值,直接读进来判断就可以了。 - HDU2509 Be the Winner
这个题因为觉得它能每次分成两堆啊所以感觉和上一个题有点不一样然后就直接上记忆化搜索了。。然而写出来以后发现一个奇怪的问题就是如果把上一个题的代码直接交上去它竟然也是AC的?然后看看SG函数,发现SG[i]=i。。为什么会这样呢。。实际上也是可以用归纳法证明的,显然SG[0]=0,然后假设0..i-1的所有SG值都等于它们本身,那么只要让当前个数为i的这一堆分成的两部分中有一个为0,那么就可以得到0..i-1的所有后继SG值了。。那么SG[i]=i就成立了。。
换个地方进行——Staircase Nim
阶梯博弈是这样一个模型:有一个n层的台阶,每个台阶上都放有一定数量的石子。每次每个玩家可以选取某一层上任意数量的石子移动到下一层,不能操作的人输。
为了解决这个问题,我们先考虑比较简单的情况。最底下那一层因为不能往下移动,上面不管有多少石子都相当于没有;而如果只有第一层有石子,那么先手可以一次把它们全都移动到底,显然这是一个先手必胜态;如果只有第二层有石子,那么只要先手移动某个数量的石子到第一层,后手就可以把它们全都移到最底下,这是一个先手必败态。
这样我们可以发现,我们可以用偶数层上的石子作为中转来维护奇数层上的石子个数不变。并且只有奇数层上的SG值会对胜负产生影响,因为如果是偶数层上的话一层层移动到最后会让后手走最后一步,那么当前玩家就没有必要维护偶数层的状态。因为有偶数层作为中转,所以先手和后手都可以维护奇数层数目不变,即维护自己的必胜态。只考虑奇数层求出SG值即可。
几个例题
- HDU-5996 dingyeye loves stone
阶梯博弈的简单模型,因为每次只能把当前节点的石子移动到它的父节点上,这和台阶的模型是类似的。 - HDU-4315 Climbing the Hill
把空格看做“石子”,模型的转化就比较显然了。但是由于题目中“King”的要求所以需要增加一些特判。 - HDU-3389 Game
这个题的关键是模型转化。实际上阶梯博弈的模型主要有三点:第一步是划分状态,就是看什么样的状态能够被划分为“一个台阶”;第二步就是寻找可以维护的不变量,就是“奇数层”和“偶数层”是什么。第三步就是确定哪一部分对最终的游戏状态会产生影响,也就是确定“奇数层”是什么。确定了这三步以后就很好做了。 - BZOJ-1115 石子游戏
简单的模型转化。
一堆变成多堆——Multi-SG
所谓Multi-SG就是每次操作完了以后一个单一游戏可能分裂成两个单一游戏。实际上这个的处理也非常方便,因为一个状态的后继变成了两个状态,而下一名玩家可以选择两个状态中的任意一个进行操作,所以影响到后继结果的应该是这两个状态构成的一个整体。那么只需要把这两个分别求出来SG值然后“加起来”也就是异或一下然后计入后继就可以了。
几个例题
- POJ2311 Cutting Game
- BZOJ2940:[Poi2000]条纹
- POJ3537 Crosses and Crosses
- BZOJ3576 江南乐
这道题需要对求解SG函数的记忆化搜索做一些优化,需要一些数学相关的知识。
考虑步数的影响——Every-SG
Every-SG游戏是指由很多单一游戏组成,每次每个玩家需要在每一个能够操作的单一游戏上进行操作,最后结束的那个单一游戏的胜负决定整个游戏的胜负。那么对于每个玩家来说,都希望自己必胜的游戏玩得尽量长,自己必败的游戏玩的尽量短。这和普通SG游戏不同的一点就是步数也会对最终结果产生影响。
那么就在求解SG函数的过程中引入记录最优步数的step,如果当前状态是必胜态,那么当前状态的step就是所有是必败态的后继的step的最大值;如果当前状态是必败态,step就是所有后继状态step的最小值。这个也比较好理解,因为每个人都会采取对自己最有利的策略。最后如果步数最大的那个单一游戏会在奇数步结束那么先手就是必胜的。
例题:HDU3595 GG and MM
博弈问题的几种方法
找规律永远是最有用的!
有些时候数据范围特别大或者直接求SG函数特别慢的时候找规律非常有用啊= =不一定要打出表来找,有时候考虑一下游戏的性质就能够发现有用的规律。
- Vijos1196 吃糖果游戏
真·找规律。纯找规律题。 - POJ2505 A multiplication game
这题一开始读错题了结果稀里糊涂写出了对的代码交上去A了。。据说zyf2000也读错题了结果稀里糊涂写出了错的代码交上去也A了。。。= = - HDU3032 Nim or not Nim?
纯打表找规律题= = - BZOJ2463 谁能赢呢?
可以发现棋盘行列数的奇偶性和游戏胜负的关系。
游戏的影响因素
有些题一眼看上去并不知道是什么东西。。但是如果考虑一下什么因素会影响游戏的胜负,什么因素不会影响游戏的胜负,再往常见的模型上转化往往能有一些效果。
Poj-1704 Georgia and Bob
题意:给出一些位置上的硬币,每次可以选择一个硬币在不跨过其它硬币的前提下往前移动任意的位置,不能移动的人输,求是否有必胜策略。每个位置的硬币只有一个,那么硬币个数肯定不是影响游戏胜负的。可以发现这个硬币能够移动的次数和它与其它硬币的相对位置有关,换句话说,就是它和前面的硬币之间还有多少个空格。注意到这一点以后就能进一步发现,当移动了某一个硬币之后,它和前一个硬币的空格数会减少,但跟后一个硬币的空格数会增多,相当于空格从前面“移动”到了后面。并且移动到最后一个硬币之后的空格不会再移动了。这就是一个阶梯博弈的模型。
1299: [LLH邀请赛]巧克力棒
这题长度范围这么大没法求SG函数,并且如果把每根巧克力棒看成是Nim游戏中的一堆“石子”的话,那么就有些“石子”能取但有些不能取。但是进一步考虑,会影响到当前游戏结果的只有那些被拿出来的巧克力棒,但玩家可以通过继续拿出新的巧克力棒来改变游戏的总SG值。那么对于先手来说就要保证当前可操作的游戏是一个必胜态,并且后手没有办法通过拿出新的巧克力棒来把必胜态抢走。从异或值方面进行考虑,可以用搜索解决。
可以考虑对称性
有时候如果能确定什么情况下游戏是必胜的,什么情况下游戏是必败的,那么题目就很简单了。而确定这一类状态非常方便的思路就是“对称性”。如果能够把游戏分成对称的两部分,那么先手在某一部分里做了某个操作,后手就可以在另一部分里做和它同样的操作。最后肯定是先手先结束他自己的那一部分,后手再结束另一部分,那么后手就胜利了。
HDU1846 Brave Game
这类题目有一个单独的分类——巴什博弈。给出一堆石子,每次每个玩家最少拿一个,最多拿m个,拿走最后一个的人赢。那么我们可以把每m+1个石子分成一组。之所以是分成m+1个一组的形式,是因为对于m+1个石子来说,不管先手在里面拿多少个,后手都可以一次把剩下的拿完。那么每m+1个石子的SG值就会是0,决定最终胜负的只是模m+1以后的那些余数。因为这个余数不超过m,那么如果余数是0的话后手通过上面的策略可以保证必胜,否则先手一定可以把剩下的一次拿走。POJ1740 A New stone game
题意:有一些石子,每次每名玩家可以从某一堆里取出一些石子,然后从这一堆石子里选取任意多的石子分给其它堆,不能操作的人输。首先可以发现如果只有一堆石子的话先手必胜。考虑两堆石子的情况,如果这两堆石子相等的话,就可以把它分成对称的两部分,后手是必胜的。推广到所有可以对称分开的情况,后手都是必胜的。再考虑有出现过奇数次的堆的情况,可以发现此时先手有了很大的主动性,可以任意维护奇数堆和偶数堆的数量,那么先手就是必胜的。
POJ2484 A Funny Game
题意:有n个硬币排成一圈,每次每名玩家可以选择相邻的一个或两个硬币拿走,不能操作的人输。这题就是简单的利用对称性,可以发现除了一个或两个硬币先手可以全部拿走,其他情况拿过一次以后都会形成一条链,而后手可以把这条链分成相等的两部分,接下来就是对称操作了。
除了Nim之外还有别的
看起来很简单——翻硬币游戏
翻硬币游戏是一大类问题,但共同的一个特点就是所翻的所有硬币中最后一个硬币必须是从正面翻到反面,这样就保证了所有正面朝上的硬币都在不停地“左移”,保证了游戏不会一直进行下去。可以发现影响翻硬币游戏结果的就是正面朝上的硬币的位置。
贴一个讲的很全的链接。
例题:HDU3537 Daizhenyang’s Coin
这个题好像有一个专门的名字叫做什么“Mock Turtles”游戏。。上面的链接里也有提到,直接用那个结论来跑就可以了。
图论相关——删边游戏
这一类游戏在jzh的论文里也有非常详细的阐述。。它指的是给出一棵树,每次可以在树上删掉一条边,和根节点脱离的部分整个被删掉,最后不能操作的人输。
结论:叶子节点的SG值为0,其它节点的SG值为子树SG值+1 的异或值。
- HDU3094 A tree game
直接利用结论来做即可。 - POJ3710 Christmas Game
这道题里出现了环。那么如果单独考虑一个环,操作一次以后它就变成了两条链。利用上面的结论可以发现奇数环的SG值为1,偶数环的SG值为0。那么把图上的环都缩成点就可以了,可以dfs也可以用Tarjan。
POJ3710数据生成器:
#include<cstring>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#define random(x)(rand()*rand()%(x)+1)
using namespace std;
int n,c,m,v[110],cnt,dlt,e[110];
int main()
{
freopen("input.txt","w",stdout);
srand(time(0));
c=4;printf("%d\n",c);
for (int wer=1;wer<=c;wer++){
n=random(5)+2;
m=n-1;dlt=n;
for (int i=1;i<=n;i++) v[i]=random(n);
cnt=unique(v+1,v+n+1)-v-1;
for (int i=1;i<=cnt;i++){
int d=random(4);
e[i]=d;m+=d+1;
}
printf("%d %d\n",dlt,m);
for (int i=1;i<n;i++) printf("%d %d\n",random(i),i+1);
for (int i=1;i<=cnt;i++){
for (int j=1,last=v[i];j<=e[i];j++){
printf("%d %d\n",last,++dlt);
last=dlt;
}
printf("%d %d\n",dlt,v[i]);
}
printf("\n");
}
return 0;
}
坑着
二分图博弈?
不平等博弈?
Nim积?
k倍动态减法游戏?
以后有心情再看叭= =