转自:https://blog.csdn.net/tt_twilight/article/details/71101783
http://blog.csdn.net/ac_gibson/article/details/41624623
http://blog.csdn.net/theprinceofelf/article/details/7225206
一. 巴什博奕(Bash Game):
A和B一块报数,每人每次报最少1个,最多报4个,看谁先报到30。这应该是最古老的关于巴什博奕的游戏了吧。
其实如果知道原理,这游戏一点运气成分都没有,只和先手后手有关,比如第一次报数,A报k个数,那么B报5-k个数,那么B报数之后问题就变为,A和B一块报数,看谁先报到25了,进而变为20,15,10,5,当到5的时候,不管A怎么报数,最后一个数肯定是B报的,可以看出,作为后手的B在个游戏中是不会输的。
那么如果我们要报n个数,每次最少报一个,最多报m个,我们可以找到这么一个整数k和r,使n=k*(m+1)+r,代入上面的例子我们就可以知道,如果r=0,那么先手必败;否则,先手必胜。
巴什博奕:只有一堆n个物品,两个人轮流从中取物,规定每次最少取一个,最多取m个,最后取光者为胜。
代码如下:
#include <iostream>
using namespace std;
int main()
{
int n,m;
while(cin>>n>>m)
if(n%(m+1)==0) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
return 0;
}
例题有:HDU4764 Stone:
题目大意:Tang和Jiang轮流写数字,Tang先写,每次写的数x满足1<=x<=k,Jiang每次写的数y满足1<=y-x<=k,谁先写到不小于n的数算输。
结论:r=(n-1)%(k+1),r=0时Jiang胜,否则Tang胜。
详解:
Bash Game:同余理论
一堆n个物品,两人轮流取,每次取1至m个,最后取完者胜
比如10个物品,每次只能取1到5个,则先手方必赢
1.面对[1…m]个局面,必胜
2.面对m+1个局面,必输
3.如果可以使对手面临必输局面,那么是必赢局面
4.如果不能使对手面临必输局面,那么是必输局面
基础:1 , 2, …, m是必赢局面, m+1是必输局面
递推:m+2,m+3, … ,2m+1是必赢局面,2m+2是必输局面
…
k(m+1)是必输局面,应该允许k=0,因为0显然也是必输局面
在必输局和必赢局中,赢的一方的策略是: 拿掉部分物品,使对方面临k(m+1)的局面
例如上例中10个物品,只能拿1到5个,先手方拿4个即可,对手无论拿多少个,你下次总能拿完
从另一个角度思考这个问题,如果物品数量随机,那么先手一方胜利的概率是m/(m+1),后手方胜利的概率是1/(m+1)
AC代码:
#include <iostream>
using namespace std;
int main()
{
int n, k;
while(1){
cin>>n>>k;
if(n==0 && k==0){
break;
}
int r = (n-1)%(k+1);
if(r==0){
cout<<"Jiang"<<endl;
}else{
cout<<"Tang"<<endl;
}
}
return 0;
}
二. 威佐夫博弈(Wythoff Game):
有两堆各若干的物品,两人轮流从其中一堆取至少一件物品,至多不限,或从两堆中同时取相同件物品,规定最后取完者胜利。
直接说结论了,若两堆物品的初始值为(x,y),且x<y,则另z=y-x;
记w=(int)[((sqrt(5)+1)/2)*z ];
若w=x,则先手必败,否则先手必胜。
代码如下:
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n1,n2,temp;
while(cin>>n1>>n2)
{
if(n1>n2) swap(n1,n2);
temp=floor((n2-n1)*(1+sqrt(5.0))/2.0);
if(temp==n1) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
}
return 0;
}
详解:
Nim Game:异或理论
m堆n个物品,两人轮流取,每次取某堆中不少于1个,最后取完者胜
详细分析见:POJ-2234:Matches Game
试题链接:http://poj.org/problem?id=2234
所有物品数目二进制异或 为0,则先手必输
所有物品数目二进制异或不为0,则后手必输
从另一个角度思考这个问题,如果物品数量随机,那么每个数目的每一位上1或0概率相同,
如果有奇数个堆,那么1的个数为偶数或者奇数的概率相同,
如果有偶数个堆,那么1的个数为偶数的概率略大1/(m+1),
也就是说异或结果的每一位为0或1的概率几乎差不多,而先手必输要求异或结果每一位都为0,其实输的概率很小
AC代码:
#include <iostream>
using namespace std;
int main()
{
int n, temp;
while(cin>>n){
for(int i=0;i<n;i++){
int num;
cin>>num;
if(i==0){
temp = num;
}else{
temp ^= num;
}
}
if(temp){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
return 0;
}
三. 尼姆博弈(Nimm Game):
尼姆博弈指的是这样一个博弈游戏:有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件,取到最后一件物品的人获胜。
结论就是:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败,否则先手必胜。
代码如下:
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n,ans,temp;
while(cin>>n)
{
temp=0;
for(int i=0;i<n;i++)
{
cin>>ans;
temp^=ans;
}
if(temp==0) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
}
return 0;
}
详解:
Wythoff Game:黄金分割
两堆(ak,bk)(ak<=bk)个物品,两人轮流取,每次从一堆中取k个或者从2堆中同时取k个,最后面对(0,0)局面的输(设ak<=bk是为了忽略顺序的影响)
1.面对(0,0)局面必输
2.面对(1,1)(2,2)…(n,n)局面必赢
(0,1)(0,2)…(0,n)局面必赢
3.如果可以使对手面临必输局面,那么是必赢局面
4.如果不能使对手面临必输局面,那么是必输局面
基础:(0,0)是必输局面;(0,1)(0 ,2)…(0,n)是必赢局面,
递推:(1,2)是必输局面;(1,1)是必赢局面
(1,3)(1 ,4)…(1,n)是必赢局面
(2,2),(2,3)…(2,n)是必赢局面
(3,5)是必输局面;(3,3)(3,4)是必赢局面
(3,6)(3,7)…(3,n)是必赢局面
(5,5)(5,6)…(5,n)是必赢局面
(4,7)是必输局面;(4,4)(4,5)(4,6)是必赢局面
(4,8)(4,8)(4,9)…(4,n)是必赢局面
(7,7)(7,8)(7,9)…(7,n)是必赢局面
(6,10)是必输局面;(6,6)(6,7)(6,8)(6,9)是必赢局面
(6,11)(6,12)(6,13)…(6,n)是必赢局面
(10,10)(10,11)(10,12)…(10,n)是必赢局面
首先发现规律:(必输局面的规律比较容易找到)
ak是前面必输局未出现的数中最小者,
bk=ak+k( k=0,1,2,3,…n)
下面介绍必输局(奇异局)的最重要性质:
1,2,…,n中每一个自然数,出现且只出现在一个奇异局中。
推导:1.由于ak总是选择未出现的数,所以每个数总能出现在奇异局中
且ak不会选择到重复的数
2.bk=ak+k,所以bk总是比前面所有奇异局出现的数都大,
所以bk不会选择到重复的数
必赢一方的策略是:始终让对手面对必输局(奇异局)
给定任意局势(a,b),判定(a,b)是否为必输局的方法是:
k=0,1…n 记黄金比例是φ=1.618033
ak=[kφ],bk=ak+k=[kφφ]
如k=0,ak=0,bk=0
k=1,ak=1,bk=2
k=2,ak=3,bk=5 k=3,ak=4,bk=7
更好的一种判断策略是 k = bk-ak ,如ak=kφ时,当前局势为奇异局
从胜负概率角度,如果堆中数量随机,先手一方优势很大
(相应经典题目是POJ-1067)
四. 斐波那契博弈:
有一堆物品,两人轮流取物品,先手最少取一个,至多无上限,但不能把物品取完,之后每次取的物品数不能超过上一次取的物品数的二倍且至少为一件,取走最后一件物品的人获胜。
结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)
如HDU2516
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 55;
int f[N];
void Init()
{
f[0] = f[1] = 1;
for(int i=2;i<N;i++)
f[i] = f[i-1] + f[i-2];
}
int main()
{
Init();
int n;
while(cin>>n)
{
if(n == 0) break;
bool flag = 0;
for(int i=0;i<N;i++)
{
if(f[i] == n)
{
flag = 1;
break;
}
}
if(flag) puts("Second win");
else puts("First win");
}
return 0;
}