必胜点和必败点的概念:
P点:必败点,处于此位置的人,在双方操作均正确的情况下必败。
N点:必胜点,处于此位置的人,在双方操作均正确的情况下必胜。
必胜点和必败点的性质:
1、终结点为必败点P
2、该点为必胜点N 等价于 该点至少有一种方式进入必败点P
3、该点为必败点P 等价于 无论如何操作,该点都只能进入必胜点N(即无法进入必败点P)
典例:HDU - 1847
题目大意:
Kiki和Cici打牌,n张牌,轮流抓 ,Kiki先抓
抓牌数只能是2的幂次(1,2,4,8,16…) 张
最后抓完牌的人获胜
于是你可以打出如下的P/N表
于是你发现规律,3的倍数的点都是必败点
当然,没发现也没所谓,这个题 n 还是很小的,确确实实利用上面的性质用循环把表打出来也是可以的
好,理解了P/N法之后我们来继续学习下面的内容
首先说句废话,就是为啥这个叫巴什博奕,原因就是这事巴什发明的数学游戏,接下来要讲的两个博弈同理
巴什博奕事实上超级简单,那么我开讲了
只有一堆n个物品,两个人从轮流中取出(1~m)个;最后取光者胜。这就是巴什的游戏
根据我们上面讲的 P/N 法实际上就可以轻松的做出来了
我就不多讲了
然后这个是有结论的。记住最好,记不住就 P/N 也就几秒钟的事。
结论是:n 为 (m+1) 的倍数时必败
bool Bash_Game(int n,int m){//1赢0输,以后的代码同理
if(n%(m+1)==0) return 0;
return 1;
}
从尼姆博弈开始,我们就要增加难度了,由单游戏向多游戏(组合游戏)转变
N堆物品,每堆有ni>0个物品,依旧是两个人来取,但是可取个数不再是 1~m 而是 1~∞
在这种情况下,问题就变得有意思了,而且还与二进制有密切关系。
对于这种多游戏博弈,我们不再用必败必胜点的说法了,而是说某种局势
奇异局势就相当于必败点,非奇异局势就相当于必胜点。
奇异局势的性质:(事实上就是必胜必败点的性质)
1、任意操作都可将奇异局势变为非奇异局势。
2、采用适当的方法,总可以将非奇异局势变为奇异局势。
然后有个非常有意思的人还专门提出一个定理
Bouton定理:先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。(平衡的尼姆博弈就是奇异局势)
(就是你面对奇异局势必败,面对非奇异局势必胜,和P/N一样,这个定理说的什么别再反复读了,知道还有这么个定理就行了)
我们用(a,b,c)表示某种局势,首先(0,0,0)显然是奇异局势
第二种奇异局势是(0,n,n),只要对手与我拿走一样多的物品,最后都将导致(0,0,0)
仔细分析一下,(1,2,3)也是奇异局势,无论自己如何拿,接下来对手都可以将其变为(0,n,n)的情形。
那么这些奇异局势共有的特点是什么呢
研究表明:任何奇异局势(a,b,c)都有a⊕b⊕c =0 (异或运算⊕,或者你用模二加法做也一样,模二加减就是异或)
并且可以扩展到 N 个数,奇异局势(n1,n2,..,nN) 有 n1⊕n2⊕...⊕nN=0
这样我们就可以直接判断某一局势是奇异局势还是非奇异局势了
那么非奇异局势是通过进入奇异局势来取胜的,那么是如何进入的呢,让我们进一步来了解一下吧
由于异或运算满足 交换律和结合律 且 a⊕a=0
于是对于 (a,b,c),让 a=b⊕c,则 a⊕b⊕c = (b⊕c) ⊕ (b⊕c) = 0
同理你也可以让 b=a⊕c 或者 c=a⊕b,n个数同理,就不多说了
bool Nimm_Game(int N){
int ans=0;
for(int i=0;i
典例:POJ - 2234 (纯Nimm博弈,一毛一样)
有两堆各a,b个物品,依旧是两个人来取,数量变为 单堆取1~∞个 或 同时从两堆中取同样多的物品。
这个游戏就比较有意思了,对于威佐夫博弈,我们可以发现他的奇异局势,前驱后继是链式的连接
我们用 (x , y) 来表示当前局势
然后 (a[ i ] , b[ i ]) 来表示 第 i 个奇异局势( i >= 0 ) ,前几个奇异局势为:
(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
发现一个威佐夫博弈中的奇异局势所具有的一条特殊性质 和 两个结论
特殊性质:任何自然数都包含在一个且仅有一个奇异局势中。
初步结论:a[0] = b[0] = 0,a[ i ] 是未在前面出现过的最小自然数,且 b[ i ] = a[ i ] + i。
而这样的结论我们无法满足,毕竟 a[ i ] 的判断并不简单
于是继续研究 ( 威佐夫博弈中所有研究与证明都请忽略吧,研究的价值不大,学会结论才是最重要的 )
然后同时又发现,b - a 就是 i
结论:令 b = max(a,b) , a = min(a,b)
若 (b - a) * (1.0 + sqrt(5.0)) / 2.0 == a 则为奇异局势(必败),否则必胜
bool Wythoff_Game(ll a,ll b){
if(a>b) swap(a,b);
ll tmp=(b-a)*(1.0+sqrt(5.0))/2.0;
if(tmp==a) return 0;
return 1;
}
然后如果要输出进入必败态的操作的话,暴力O(n)跑 i 就行
典例:HDU - 1527 (纯Wythoff博弈,一毛一样)
n 个砂子,两人轮流取,开局第一次可取 1 ~ n-1 个,从第二次开始每次可取 1~上一次*2 个
比如我先取了 2 个,轮到你取,你可以取 (1 ~ 4)个,你取了 3 个,然后轮到我取 (1 ~ 6)个
这个博弈,就比较特别了,他对于同一种状态,由于进来的方式不同,接下来可操作的方式也不同,之后可进入的状态就不同,所以必胜必败关系就不同,PN、SG这类通用方法在此全部失效。
那么怎么办呢,斐波那契给了我们结论
结论:n为斐波那契数则必败
斐波那契数:1,2,3,5,8,13,21,34,55...
典例:HDU - 2516
然后关于这个斐波那契博弈,实际上它是一类游戏,称作--k倍动态减法游戏
关于证明斐波那契结论,我们不讲,因为没有意义
我们要讲,他的原理到底是怎样的,如何去推导,因为你知道这时候的PN/SG不好用了,那么我们怎么推导
也就是说我们要讲的就是k倍动态减法游戏如何推导
① k=1时
我们可以把每一时刻的 n 拆成二进制,你只要取走这个二进制的最后一个1,他就无法取走倒数第二个1,你就必胜
什么意思呢,举个例子
n=12=1100,比如你取走4个,剩 8=1000,对方可以取1~4个,那么他肯定取不掉8个,现在比如我们假设他取 3 个吧
剩 101 个,你可以取 1~3 个,你取一个,剩 4=100,那么它依然不能取到前面的 1 ,然后接着反复做这样的操作
很明显你必胜
但是
如果一开始 n 是1000这种只有一个1的呢
那你不管怎么取,总会出现最右的1,让对方可取,从而让对方进入必胜态,所以你必败
于是结论:k=1,2^i 必败
然后实际上你每次取的数也是来自必败态的,都是2的次幂嘛,这句话我们下面的情况也会用到
②k=2时
我们已经知道结论是必败点是斐波那契数,那么为什么斐波那契数就是必败点呢
有一个性质你需要知道:任意整数n都可以写成若干项不相邻的斐波那契数的和
且 f [ i + 2] > 2*f [ i ]
斐波那契数 1 2 3 5 8 13...
比如 12 = 8 + 3 + 1,把取或不取按下标顺序写作二进制数就是 10101
那么你取 1 个,对方就不能取走 3 个,接下来反复取最右一个1的这样的操作,就是你必胜
和k=1的情况是一样的,所以说斐波那契数必败
③k>=3时
那么仿照 k = 2 的情况,我们逆向去推 一个 数列(k=2是斐波那契数列) 该怎么推呢
首先想这样一个问题, 任意整数n都可以写成若干项不相邻的斐波那契数的和
换句话说就是 斐波那契数列里的数 组合一下 就可以得到不属于斐波那契数列里的数
比如 4 = 1 + 3 = 101,换句话说 4 就是可构造数,再换句话说 1=1 2=10 3=100 5=1000,就是不可构造数
那么再比如 5~8 之间的数6,7一定都是可构造数,而且一定是用前面的 1 2 3 5 构造的,因为 8 自己都比他大
那么再想一下, 1 2 3 5 能够造出来的最大的数是多少呢
这时候又涉及另一个问题了,我们说构造时要用不相邻项,为什么呢
这是因为f [ i + 2] > 2*f [ i ],我们其实是要这个 2 倍关系,不是要不相邻,只不过在斐波那契里恰好隔至少一个就行
说到底,我们要的是k=1里说的,取了最右的一个1,就让对方无法取下一个1
也就是差超过 k 倍
换句话说,你设计的这个数列,可选中其中一些数,且任意相邻两数相差大于k倍,从而构造出可构造数
那么比如 1 2 3 5,构造出来最大的就是 2+5 = 7,下一个斐波那契数显然是构造不出来的,而每个自然数讲道理应该都能取得到,这是显然的,那么下一个斐波那契数就是 7 + 1 = 8
那么很明显,用动态规划的思想,对一个已经维护好的 a1~ai 数列
再维护一个 b1~bi 表示使用 a 数列前 i 个数构造的最大可构造数,那么 a[ i + 1] 就= b[ i ] + 1
那么求 a[ i + 2] 就需要 b[ i + 1] ,那么 b[ i + 1] 怎么求呢
我们用 a 数列前 i 个数构造出最大的可构造数是 b[ i ],又知道 a[ i + 1] 比 b[ i ] 大,而显然 b[ i + 1]要构造的更大
所以一定会用到 a[ i+1 ] ,我们又知道相邻超过k倍,那么就找一个最大的 a[ t ],使得 a [ t ] * k < a[ i + 1]
很明显只要用数列 a 1~t 范围内的数所构造的最大的可构造数 b[ t ],然后 b[ i + 1] = b[ t ] + a [ i + 1]
这样我们就可以一直推下去,构造出这个必败点数列了
是不是很简单呢?下面给出 k倍动态减法游戏模板(HDU - 2486,含输出第一步操作)
#pragma GCC optimize("Ofast")
#include
using namespace std;
using ll=long long;
int t,n,k;
ll a[1000005]={1},b[1000005]={1};
int main(){
scanf("%d",&t);
for(int cas=1;cas<=t;cas++){
scanf("%d%d",&n,&k);
int i,j;
for(i=0;a[i]=a[i]) break;
if(a[j]*k=a[i]) ans=a[i],n-=a[i];
i--;
}
printf("Case %d: %lld\n",cas,ans);
}
return 0;
}
学会了 P/N法 和 经典博弈 后,就该来谈谈我们的 SG函数和SG定理 了
SG定理是用来解决 多游戏(组合游戏) 的标准方法
SG定理:多游戏的SG函数值等于各个游戏SG函数值的Nim和。(Nim和即模二相加和,即异或和)
讲SG函数前,我们先来讲一下mex(minimal excludant)运算
mex运算:施加于集合,表示最小的不属于这个集合的自然数。例:mex{0,1,2,4}=3、mex{2,3,5}=0、mex∅=0。
SG函数公式: 令 RES(n) 表示状态 n 的所有后继状态构成的集合 (即 n 被取后剩余数量构成的集合)
令 SG(0) = 0,对 r1,r2,...,rcnt ∈RES(n) 有
SG(n) = mex{ SG(r1) , SG(r2) , ... , SG(rcnt) }
例如:有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子
n=1时,RES = { 0 },SG( 1 ) = mex { SG (0) } = mex{ 0 } = 1
n=2时,RES = { 1 },SG( 2 ) = mex { SG (1) } = mex{ 1 } = 0
n=3时,RES = { 0 , 2 },SG( 3 ) = mex { SG (0) , SG(2) } = mex{ 0 } = 1
n=4时,RES = { 0 , 1 , 3 },SG( 4 ) = mex { SG (0) , SG(1) , SG(3) } = mex{ 0 , 1 } = 2
n=5时 , RES = { 1 , 2 , 4 },SG( 5 ) = mex { SG (1) , SG(2) , SG(4) } = mex{ 0 , 1 , 2 } = 3
注:SG函数是单游戏递推得到的函数,对于单游戏,SG 函数完全可以取代 P/N 法
SG函数值 非0 时 必胜,SG函数值为 0 时 必败。多游戏可用 SG 定理求得 和SG函数,进而判断出必胜必败情况
SG函数求取模板:
int sg[MAXN],f[N],vis[MAXN];//f[N]为N种可取个数(从小到大) vis标记RES对应的sg值
void get_sg(){
memset(sg,0,sizeof sg);
for(int i=1;i
典例:HDU - 1848
1、PN法本质是SG函数的弱化,优点是逻辑推断思维判断中,PN法直接且有效、高效,缺点是无法使用SG定理即无法处理多游戏的组合游戏
2、nimm博弈本质上是n个sg[x]=x的组合游戏
3、斐波那契博弈应该无法使用任何基本博弈方法推理证明,因为同一局面下可操作手段不同,是变化的,反观其他情况,即便操作方法不确定,但是同一局面总是与同一系列操作一一对应着
4、威佐夫博弈本质上可以由二维sg[x][y]暴力打表推断规律,但是它无法拆分成两个子游戏的组合,因为它是杂交型游戏,我从两堆取同样多的石子。你可能认为是一个操作一下改变两个子游戏的状态才不行,但是注意不是因为这个原因,不是因为单操作影响多子游戏才使得他无法拆分。真正原因就是他取同样多时,取多少,与两个游戏的状态都有关,比如我现在认为我在操作y,那么我想做同取操作需要看x是多少,是这个原因,不是因为x也被一起改变。所以说威佐夫博弈无法拆分,从而无法使用SG定理。但是,包含型重复游戏(可以影响其他子游戏状态,但是其他子游戏状态无法影响当前子游戏操作)是可以拆分并使用SG定理的,这个要注意!!例如HDU - 3537。他是看做一个正面硬币一个游戏,这个正面硬币不同的初始位置对应不同状态。然后多个这样的单硬币游戏组合成题里说的游戏。这种的,他的子游戏会互相影响,因为他翻一串可以把前面的游戏也翻了,前面的正面一翻状态被改变了,一次操作影响多个子游戏,但是无所谓,我当前这个子游戏该怎么操作就怎么操作,操作不会被你别的子游戏的状态影响,你可能会说后续状态不对应,后续的操作变了啊,不用管那个,后续操作和后序状态对应,接下来怎么做不关你事,你当前没问题就无所谓,你站在每个过去每个未来上都是当前,当前没问题就是总的没问题。好好理解我说的,这个一定要搞清楚。