引入
经典的NIM博弈
给若干堆石子,Alice和Bob轮流取石子,每次可选择其中的一堆拿走任意多的石子,但不能不拿。最后没有石子可拿的人输。
已知:堆数N,每堆石子数目
ai
,Alice先取。
求:谁是胜者。
博弈基于足够聪明的两者,每次都尽量取到必胜的状态,绝不存在其中一人明明可以胜出却要让别人赢的情况。因此输赢往往由局面本身确定。即存在必胜态
、必败态
。
上面这个游戏的必败态,即玩家面对的是没有任何石子的情况。
能够经过一步操作存在必败态的状态,就一定是
必胜态
。
经过一步操作全是必胜态的状态,一定是必败态
。
可以发现
a1⨁a2⨁...⨁an=1
为必胜态
a1⨁a2⨁...⨁an=0
为必败态
MEX函数
MEX的运算对象为一个集合S,MEX(S)即最小的不属于S的非负整数。如
S | MEX(S) |
---|---|
ϕ | 0 |
{1,2,3} | 0 |
{0,1,4} | 2 |
定义
对于一个给定的有向无环图,图上每个点表示一种局面或状态。
定义关于图的每个顶点的SG函数如下
当∃y,SG(y)=0,则SG(x)≠0
当∀y,SG(y)≠0,则SG(x)=0
这个描述和上面的必胜必败态完全一致,可见当 SG(x)=0 时,x即为一个必败态。
若有若干个平行局面共同组成了一个游戏,则这个父局面的状态由子局面的SG值决定
应用
终于到了做题环节!
HDU 5724 Chess
今年的第一把多校,结果三个人都没看这题(过题人数第二多的样子T.T)
题意:n×20的棋盘内放置了一些棋子,每颗棋子走到向右的最近的空格为一次操作,Alice和Bob轮流下棋,无路可走的就输。Alice先手。问Alice会不会输呢?
每行都是独立的,只要计算出每行状态的SG函数值,异或即得到答案。
一行中若有x个棋子,则它可以到达的状态y即有x个,通过
SG(x)=MEX{SG(y)|x→y}
打表。
通过x找到y的过程,可以用数组模拟,但是又慢又蠢啊。其实交换0和1之后其余位的和没有变,可以节省一半的时间。先找1再找后面最近的0,和先找0再找前面连续的1,时间也差很多啊。不过出题人还是良心的,最慢的重复算也没有超时(险过…)
附上优雅的打表…
for(int i = 1;i < (1<<20); i++){
int h[25];
memset(h, -1, sizeof(h));
int last = -1;
for(int j = 0; j < 20; j++){
if(!((i >> j) & 1))
last = j;
if(((i >> j) & 1)){
if(last != -1){
h[sg[(i ^ (1 << j)) ^ (1 << last)]]=1;
}
}
}
int j=0;
while(h[j] != -1) j++;
sg[i]=j;
}