博弈论
一、 巴什博奕(Bash Game)
巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物, 规定每次至少取一个,最多取m个。最后取光者得胜。
分析:
显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的法则:如果n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
结论:若n%(m+1)!=0 先手胜
对于巴什博弈问题代码如下:
1 #include <iostream>
2 using namespace std;
3 int main()
4 {
5 int n,m;
6 while(cin>>n>>m)
7 if(n%(m+1)==0) cout<<"后手必胜"<<endl;
8 else cout<<"先手必胜"<<endl;
9 return 0;
10 }
11
这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜。
对于巴什博弈,如果我们规定,如果最后取光者输,那么又会如何呢?
(n-1)%(m+1)==0则后手胜利
先手会重新决定策略,所以不是简单的相反行的
此问题可以这样考虑:因为输的那个人必定最后只取了一个,那么赢的那个人最后必定取k个(k<=m)所以问题可以转化为共有n-1个物品,最多能取m个,最后取光者获胜,所以当(n-1)%(m+1)==0,后手必胜。
例如n=15,m=3
后手 先手 剩余
0 2 13
1 3 9
2 2 5
3 1 1
1 0 0
此问题14%4!=0,最后一个是后手取,所以先手胜利,后手输,输的人最后必定只抓走一个,如果>1个,则必定会留一个给对手
二、 威佐夫博弈(Wythoff Game)
威佐夫博弈 :有两堆各若干个物品,两个人轮流从某一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
分析:
我们用(a[k],b[k]) (a[k] ≤ b[k] ,k=0,1,2,......n)来表示两堆物品的数量,并且称这个为局势。
首先我们来从最简单的情况开始分析:
如果现在的局势是(0,0),很明显此时已经没有办法再取了,所以肯定是之前的人在上一局中取完了。
假设现在的局势是(1,2),那么先手只有四种取法。
(1) 如果先手取走“1”中的1个,那么后手就从“2”中取出2个,此时取完,所以后手胜利。
(2)如果先手取走“2”中的2个,那么后手取走“1”中的1个,此时取完,后手胜利。
(3)如果先手取走“2”中的1个,那么后手就在两堆中各取走1个,此时取完,后手胜利。
(4)如果先手在“1”和“2”各取走了1个,那么后手取走“2”中的1个,此时取完,后手胜利。
由此可得,先手必输。
是不是觉得这个后手好厉害,无论先手怎么取,后手都会胜利。
在学习威佐夫博弈之前,我也是这样认为的。不过,当你继续看完这篇博客,你也会轻松获得胜利。
为了让大家更好地理解威佐夫博弈,我们继续来进行具体分析。
假设现在的局势是(3,5),首先根据上面分析的经验,我们知道先手肯定不能把任意一堆物品取完,这是因为每次可以从任意一堆取走任意个物品,那么后手就可以直接把另一堆取完,所以后手获胜。
所以我们这里就不分析那些情况,来分析其他的情况。
先看在一堆中取的情况:
(1) 假设先手在“3”中取1个,后手就可以在“5”中取走4个,这样就变成了(1,2)的局势,根据上面的分析,我们知道是先手输,后手获胜。
(2) 假设先手在“3”中取2个,后手就可以在 “5” 中取走3个,这样也变成了(1,2)的局势了,还是先手输,后手获胜。
(3)假设先手在“5”中取1个,后手就在 “3”和“5” 中各取走2个,这样又成了(1,2)的局势了,先手输,后手赢。
(4)假设先手在“5”中取2个,后手就在 “3”和“5” 中各取走3个,这样变成了(0,0)的局势,先手输,后手赢。
(5)假设先手在“5”中取3个,后手就在 “3”和“5” 中各取走1个,也变成了(1,2)的局势,先手输,后手胜利。
(6)假设先手在“5”中取4个,后手在“3”中取走1个,还是(1,2)的局势,先手输,后手赢。
我们发现上面列举的这几种局势,无论先手怎么取都是后手赢。
我们可以来找找那些先手必输局势的规律
第一个(0,0)
第二个(1,2)
第三个(3,5)
第四个(4 ,7)
第五个(6,10)
第六个 (8,13)
第七个 ( 9 , 15)
第八个 ( 11 ,18)
第n个 (a[k],b[k])
我们把这些局势称为“奇异局势”
我们会发现他们的差值是递增的,分别是0,1,2,3,4,5,6,7......n
有兴趣的读者可以自己模拟一下过程进行验证
我们用数学方法分析发现这些局势的第一个值是未在前面出现过的最小的自然数。
继续分析我们会发现,每种奇异局势的第一个值总是等于当前局势的差值乘上1.618
我们都知道0.618是黄金分割率。而威佐夫博弈正好是1.618,这就是博弈的奇妙之处!
即 a[k] = (int) ((b[k] - a[k])*1.618) 注:这里的int是强制类型转换,注意这不是简单的四舍五入,假如后面的值是3.9,转换以后得到的不是4而是3,也就是说强制int类型转换得到的是不大于这个数值的最大整数。
在编程题中,有些题目要求精度较高,我们可以用下述式子来表示这个值
1.618 = (sqrt(5.0) + 1) / 2
直接说结论了,若两堆物品的初始值为(x,y),且x<y,则另z=y-x;
记w=(int)[((sqrt(5)+1)/2)*z ];
若w=x,则先手必败,否则先手必胜。
对于威佐夫博弈问题代码如下:
1 #include <cstdio> 2 #include <cmath> 3 #include <iostream> 4 using namespace std; 5 int main() 6 { 7 int n1,n2,temp; 8 while(cin>>n1>>n2) 9 { 10 if(n1>n2) swap(n1,n2); 11 temp=floor((n2-n1)*(1+sqrt(5.0))/2.0); 12 //floor(x),也写做Floor(x),其功能是"向下取整",或者说"向下舍入",即取不大于x的最大整数(与"四舍五入"不同) 13 if(temp==n1) cout<<"后手必胜"<<endl; 14 else cout<<"先手必胜"<<endl; 15 } 16 return 0; 17 }
威佐夫博弈 参考博主「初雪与你」
原文链接:https://blog.csdn.net/qq_41311604/article/details/79980882
三、尼姆博弈(Nimm Game)
尼姆博弈模型:大致上是这样的:
有3堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取1个,多者不限,最后取光者得胜。
由此可知:
1. 无论谁面对奇异局势 则必输,
2. 奇异局势,取若干个,变成非奇异, 非奇异也能取若干个到奇异局势,
3.奇异局势 结果 a^b^c^...=0 结果必为0
如何 从非奇异到奇异 a^b^c=0, 把c 变成 a^b 这是可以的 因为a^a =0 ;
然后 c-a^b 就是取走的个数
结论就是:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败,否则先手必胜。
1 int main()//A先手 2 { 3 int x,n,m,i,sum; 4 scanf("%d",&n); 5 sum=0; 6 for(i=0;i<n;i++) 7 { 8 scanf("%d",&x); 9 sum^=x; 10 } 11 if(sum) 12 printf("A胜\n");//此时sum!=0,说明A面对的不是奇异局面 13 else 14 printf("B胜\n"); 15 return 0; 16 }
再来看几个具体例题:
Being a Good Boy in Spring Festival
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧
陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐
如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
1 #include<iostream> 2 #include<string.h> 3 using namespace std; 4 int main() 5 { 6 int n,p[111]; 7 while(cin>>n&&n) 8 { 9 int sum=0; 10 for(int i=0;i<n;i++) 11 { 12 scanf("%d",&p[i]); 13 sum^=p[i]; 14 } 15 if(!sum)//奇异局势 16 { 17 printf("0\n"); 18 continue; 19 } 20 else 21 { 22 int cnt=0; 23 for(int i=0;i<n;i++) 24 { 25 if((sum^p[i])<p[i])//此方案下取p[i]-sum^p[i]张形成奇异局势 26 cnt++; 27 } 28 printf("%d\n",cnt); 29 } 30 } 31 return 0; 32 }
阶梯博弈
首先是对阶梯博弈的阐述...博弈在一列阶梯上进行...每个阶梯上放着自然数个点..两个人进行阶梯博弈...每一步则是将一个集体上的若干个点( >=1 )移到前面去..最后没有点可以移动的人输..

假设我们是先手...所给的阶梯石子状态的奇数堆做Nim先手能必胜...我就按照能赢的步骤将奇数堆的石子移动到偶数堆...如果对手 也是移动奇数堆..我们继续移动奇数堆..如果对手将偶数堆的石子移动到了奇数堆..那么我们紧接着将对手所移动的这么多石子从那个奇数堆移动到下面的偶 数堆...两次操作后...相当于偶数堆的石子向下移动了几个..而奇数堆依然是原来的样子...即为必胜的状态...就算后手一直在移动偶数堆的石子到 奇数堆..我们就一直跟着他将石子继续往下移..保持奇数堆不变...如此做下去..我可以跟着后手把偶数堆的石子移动到0..然后你就不能移动这些石子 了...所以整个过程..将偶数堆移动到奇数堆不会影响奇数堆做Nim博弈的过程..整个过程可以抽象为奇数堆的Nim博弈...
其他的情况...先手必输的...类似推理...只要判断奇数堆做Nim博弈的情况即可...
为什么是只对奇数堆做Nim就可以...而不是偶数堆呢?...因为如果是对偶数堆做Nim...对手移动奇数堆的石子到偶数堆..我们 跟着移动这些石子到下一个奇数堆...那么最后是对手把这些石子移动到了0..我们不能继续跟着移动...就只能去破坏原有的Nim而导致胜负关系的不确 定...所以只要对奇数堆做Nim判断即可知道胜负情况...
为什么不对偶数堆做Nim也可以这样理解
我们明显地可以发现这样的现象。
当只剩下偶数列有数值的时候。先手必输。后者必赢。
因为先手只能做将偶数列的物品移动到奇数列上。此时后手只要把同样数量的奇数列的值移动到下一个偶数列上。
最后胜利的操作一定是将奇数列上的物品移动到0上。所以一定是后手胜。
对于这个特点。可以推出为什么对偶数列的操作是没有意义的。
既然只剩下偶数列的情况是必输的情况。那么我们博弈的时候。一定会想方设法让只有偶数列的情况留给别人。
也就是说。必胜的情况是。操作最后一次奇数列。(这里的最后是指操作了之后就只有偶数列的时候。)
对于目标。操作最后一次奇数列就是赢的。以及Nim增加操作是没有意义的。(回想一下Nim中 如果加入增加一堆上物品的操作。也是不影响结果的。因为先手如果增加了。后手可以删除同样的数量的在同样的堆上。)
而操作偶数列 就相当于给奇数列增加数量。
而且我们的目标是争夺最后一次奇数列的操作。
题目内容:
甲乙两人下一种棋。棋盘是一个n个连续的方格子,棋盘上随机放着m个棋子,一个格子只能放一个棋子。玩家轮流选择一个棋子移动,移动只能从右向左,至少要移动一个格子,但不能跨越其他的棋子和第一个格子,最后无法移动格子的玩家失败。甲始终先移动。对任何一种初始局面,你能判断谁能赢?
输入描述
第一行是测试用例的数目c, 下面的行是各测试用例二行是m个整数,
每个测试用例的第一行是整数m(<=1000),表示有m个棋子,第表示每个棋子的方格编号(左到右从1开始编号)
输出描述
每个测试用例中赢的一方。
“甲”或者“乙”或者“不确定”
输入样例
2
3
1 2 3
8
1 5 6 7 9 12 14 17
输出样例
甲
将一个棋子向左移动时,它和前面棋子的距离变小,和后面棋子的距离变大,并且减小的值和增大的值是相等的,因此,这个过程我们就可以等价成一个阶梯博弈了。把m个方格两两配对,形成一个组,即a[i]和a[i+1]是一个组,a[i+2]和a[i+3]是一个组,把a[i]和a[i+1]的距离当成阶梯博弈中的奇数阶的值,a[i+1]和a[i+2]的距离当成偶数阶(不用考虑)。
#include<iostream> #include<algorithm> using namespace std; int main() { int c,m,sum,a[1001]={0}; cin>>c; while(c--) { cin>>m; sum=0; for(int i=1;i<=m;i++) cin>>a[i]; sort(&a[1],&a[m+1]); for(int i=1;i<=m;i+=2) { sum^=(a[m-i+1]-a[m-i]-1);//奇数阶梯做Nim博弈 } if(sum==0) cout<<"乙"<<endl; else cout<<"甲"<<endl; } return 0; }
四、 斐波那契博弈
斐波那契博弈模型,大致上是这样的:
有一堆个数为 n 的石子,游戏双方轮流取石子,满足:
1. 先手不能在第一次把所有的石子取完;
2. 之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。
约定取走最后一个石子的人为赢家,求必败态。
n = 2时输出second;
n = 3时也是输出second;
n = 4时,第一个人想获胜就必须先拿1个,这时剩余的石子数为3,此时无论第二个人如何取,第一个人都能赢,输出first;
n = 5时,first不可能获胜,因为他取2时,second直接取掉剩下的3个就会获胜,当他取1时,这样就变成了n为4的情形,所以输出的是second;
n = 6时,first只要去掉1个,就可以让局势变成n为5的情形,所以输出的是first;
n = 7时,first取掉2个,局势变成n为5的情形,故first赢,所以输出的是first;
n = 8时,当first取1的时候,局势变为7的情形,第二个人可赢,first取2的时候,局势变成n为6得到情形,也是第二个人赢,取3的时候,second直接取掉剩下的5个,所以n = 8时,输出的是second;
…………
从上面的分析可以看出,n为2、3、5、8时,这些都是输出second,即必败点,仔细的人会发现这些满足斐波那契数的规律,可以推断13也是一个必败点。
借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。n=12时,只要谁能使石子剩下8且此次取子没超过3就能获胜。因此可以把12看成8+4,把8看成一个站,等价与对4进行"气喘操作"。又如13,13=8+5,5本来就是必败态,得出13也是必败态。也就是说,只要是斐波那契数,都是必败点。
所以我们可以利用斐波那契数的公式:fib[i] = fib[i-1] + fib[i-2],只要n是斐波那契数就输出second。
结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)
例题:
Problem Description
参看Sample Output.
#include<iostream> using namespace std; int main() { int n,fib[45]; int i,flag; fib[0]=2; fib[1]=3; for(i=2;i<45;i++) fib[i]=fib[i-1]+fib[i-2]; while(cin>>n&&n) { flag=0; for(i=0;i<45;i++) if(fib[i]==n) { cout<<"Second win\n"; flag=1; break; } if(flag==0) cout<<"First win\n"; } return 0; }
五、N-P图
定义 P 表示的是:走到当前位置时,我的上一个选手必赢的状态,即我在当前回合无论怎么走,都是必败的,故 P 表示的是必败点。
定义 N 表示的是:走到当前位置时,我的上一个选手必输的状态,即我在当前回合中有至少一种走法,能使自己获胜,在双方都聪明的情况下,即我必赢的状态,故 N 表示的是必胜点。
换句话就是说:
走到 P 点的人,是必败的;而走到 N 点的人,是必胜的。
对于 N-P 图有以下一些准则:
1、对于 “走不动的选手是输” 的时候,当走到 “无法移动的状态”,即终结态时,标记为 P 点。因为走到终结点的人就无法移动了,是必败态。
2、必败点 P 只能进入到必胜点 N 。
3、必胜点 N 既可以进入必败点 P,也可以进入必胜点 N 。
这里我们对上面做一个总结:
1、只能走到 N 点的,是 P 点。
2、同样,如果能走到 P 点,是 N 点。(它不可能是 P 点,因为只有 N 点才能进入 P 点)
2、既能走到 N 点又能走到 P 点的, 是 N 点。
这样我们可以通过这三个准则画出一个基本的 NP图了~
例题:HDU 2147
题目大意:给你一个 n*m 的棋盘,有一颗棋子在最右上角,两个人只能移动棋子到 左边、下面以及左下角。最后无法移动棋子的人输。在双方足够聪明的情况下,问你先手和后手谁赢。
首先, (n,1) 这个点是终结态,无论谁处于这个局势,都将无法移动棋子,则设为 P 。
其次,由于进入 P 点的只能是 N 点,故将 P 的上面一格和右边一格以及右上边一格都设为 N 。因为这三个点都可以一步进入 P 点,而只有 N 点才可以进入 P 点 。
| N | N | |||
| P | N | A |
再来看,对于下边界:
由于只能往左走(因为棋子能往 左、下、左下走,而对于下边界,其它两种情况不通)。故对于表格中: P N A ,A 点只能进入 N 点,故 A 是 P 。
对于左边界同理,故我们可以接着画:
| P | ||||
| N | ||||
| P | ||||
| N | N | |||
| P | N | P | N | P |
同样,我们对其它格子进行处理可以得到:
| P | N | P | N | P |
| N | N | N | N | N |
| P | N | P | N | P |
| N | N | N | N | N |
| P | N | P | N | P |
故这就是该题在 5*5 棋盘下的 N-P图了。
对于每一个以 (1,m) 为起点的局势:我们看它是 P 还是 N ,由于在 (1,m) 时,是先手先走,若此时 (1,m) 是 P 态,说明先手此时是 必败态,故这个局势下,先手必败,同样,起点处于 N 态 ,则先手必胜。
我们很容易的发现,如果 n 为奇数且 m 为奇数,则 (1,m) 即起点是 P 态,先手必败,反则必胜。这题就可以解决了~

浙公网安备 33010602011771号