博弈类的核心在于获取sg函数。
对于一个局面,若至少有一种操作使它变为P局面,则它是N局面
对于一个局面,无论如何操作都使它变为N局面,则它是P局面
将游戏中间的状态看做顶点,将状态的转移看做边
sg(v) 定义为 没有出现在{ sg(v1)..sg(vt) }中的最小自然数
初始节点(必须是P节点)没有出边,sg = 0.
若v有边指向某sg(vi) = 0的vi,则sg(v)>0
若v没有边指向某sg(vi) = 0的vi,则sg(v) = 0
所有P节点sg = 0
所有N节点sg > 0
sg(<n1, n2>) = sg(n1)^sg(n2) 为两个一维sg函数的异或
一、题目转化
1. 将之间的空格数看作石子数,再进行
2. 阶梯博弈,将棋子两两一对,若为奇数则将起点算入。可以理解为奇数区间内的两个石子的移动可以通过更改转换
分析这题的P/N点时,和往常不一样,不是从小数据推测,而是直接从性质推测,从右往左,把棋子分为两两一组,如果是奇数颗棋子,就补第一颗的位置为0,当对手移动两颗棋子的左边的棋子时,我们只需要把右边这颗棋子移动相同的步数就行,当对手移动右边的棋子时,把两颗棋子之间的距离看为石子的个数,我们只需按照取石子的规则走就行。
和取石子很像,和取石子的区别在于每一次移动时都会让右边相邻的那颗棋子移动空间变大,这样就和取石子只减不增有所不同了.
将棋子从右端向左端每相邻两个分为一对,如果只剩一个就将棋盘左端加一格放一颗棋子与之配对,这样配对后好像和以前没有什么区别,但决策时就方便多了,因为我们大可不必关心组与组之间的距离,当对手移动一组中靠左边的棋子时,我们只需将靠右的那一颗移动相同步数即可!同时我们把每一组两颗棋子的距离视作一堆石子,在对手移动两颗棋子中靠右的那一颗时,我们就和他玩取石子游戏,这样就把本题与取石子对应上了。
本例说明有许多模型看似复杂,但经过一些巧妙的变换,便可以转化成一些我们熟悉的模型,同时也充分体现了博弈的灵活。
如果说前面的例子是介绍这种思想的运用的话,下面的方法就是讲这种思路的优越性了,因为这一题并不是走的“手推小数据=〉猜想=〉证明”的老路,而是直接利用性质推导必败条件。
由于任何两个相邻的棋子只与他们之间的空位有关,所以可以转化为普通的Nim游戏:我们可以把这些空位看作是石子数,谁取得了最后一个空位,谁就是赢家
3.
二、sg常用求法
1.NIM游戏
直接全部异或,0则后首胜,反之先手胜。求如何选取才能取胜
void solve()
{
int i,k,j;
int ans=A[0];
for(i=1;i<N;i++)
ans^=A[i];
if(ans==0)
{printf("No\n");return ;}
printf("Yes\n");
for(i=1;i<32;i++)
if((1<<i)&ans )
k=1<<i;//得到最高位为1
j=0;
for(i=0;i<N;i++)
if(A[i]&k)
B[j]=(A[i]^ans),C[j]=A[i],j++;
for(i=0;i<j;i++)
printf("%d %d\n",C[i],B[i]);//输出从C[i] 中取,剩下了B[i]
}
2.
.每次选取石子的方案有几种。
int sg[10000+10];
int mex(int n) {
bool vis[maxn] = {false};
int i;
for(i=0; i<LA; i++) {
int t = n - A[i];
if(t < 0) continue;
if(sg[t] == -1) sg[t] = mex(t);
vis[sg[t]] = true;
}
for( i=0; ; i++) if(!vis[i]) return i;
}
3.