总结自kuangbin大神博客和其他查到的资料
理论基础
1、定义P-position和N-position:其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。
(1).无法进行任何移动的局面(也就是terminal position)是P-position;
(2).可以移动到P-position的局面是N-position;
(3).所有移动都导致N-position的局面是P-position。
2、P/N状态有如下性质:
(1)、若面临末状态者为获胜则末状态为胜态否则末状态为必败态。
(2)、一个局面是胜态的充要条件是该局面进行某种决策后会成为必败态。
(3)、一个局面是必败态的充要条件是该局面无论进行何种决策均会成为胜态
3、P点: 即必败点,某玩家位于此点,只要对方无失误,则必败;
N点: 即必胜点,某玩家位于此点,只要自己无失误,则必胜。
1 Bash Game
#include <bits/stdc++.h>
using namespace std;
/** 巴什博弈
【只有一堆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)的倍数,就能最后获胜。
**/
int main(int argc, char const *argv[]) {
int n, m;
while (~scanf("%d%d", &n, &m)) {
puts( n%(m+1) == 0 ? "后手胜" : "先手胜");
}
return 0;
}
2 Wythoff Game
#include <bits/stdc++.h>
using namespace std;
/** 威佐夫博弈
【有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。】
这种情况下是颇为复杂的。我们用(ak,bk)(ak ≤ bk ,k=0,1,2,…,n)表示两堆物品的数量并称其为局势,
如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:
(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak + k,奇异局势有如下三条性质:
1。任何自然数都包含在一个且仅有一个奇异局势中。
由于ak是未在前面出现过的最小自然数,所以有ak > ak-1 ,而 bk= ak + k > ak-1 + k-1 = bk-1 > ak-1 。所以性质1。成立。
2。任意操作都可将奇异局势变为非奇异局势。
3。采用适当的方法,可以将非奇异局势变为奇异局势。
假设面对的局势是(a,b),若 b = a,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);
如果a = ak ,b > bk,那么,取走b – bk个物体,即变为奇异局势;
如果a = ak ,b < bk ,则同时从两堆中拿走 ak – ab + ak个物体,变为奇异局势( ab – ak , ab – ak+ b – ak);
如果a > ak ,b= ak + k,则从第一堆中拿走多余的数量a – ak 即可;
如果a < ak ,b= ak + k,分两种情况,
第一种,a=aj (j < k),从第二堆里面拿走 b – bj 即可;
第二种,a=bj (j < k),从第二堆里面拿走 b – aj 即可。
从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
奇妙的是其中出现了黄金分割数(1+√5)/2 = 1.618…
因此,由ak,bk组成的矩形近似为黄金矩形,由于2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],
若a=[j(1+√5)/2],那么a = aj,bj = aj + j,
若不等于,那么a = aj+1,bj+1 = aj+1+ j + 1,
若都不是,那么就不是奇异局势。然后再按照上述法则进行,一定会遇到奇异局势。
**/
int main(int argc, char const *argv[]) {
int a, b;
double k = (sqrt(5) - 1.0)/2.0;
while (~scanf("%d%d", &a, &b)) {
if(a > b) swap(a, b);
int j = a*k;
if(a != (int)(j*(k+1))) j++; // 此时k+1 = (sqrt(5) + 1.0)/2.0;
puts(a + j == b?"后手胜" :"先手胜");
}
return 0;
}
3 Nim Game
Nim的证明就不赘述了,作为鶸我只是把证明看了看并没有深入挖掘什么,感觉以Nim的结论向外推广才是重点。。。
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char const *argv[]) {
int a, b c;
cin >> a >> b >> c;
puts(a^b^c ? "先手赢" : "后手赢");
return 0;
}