算法竞赛模板 博弈
①巴什博弈(Bash Game)
有n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。
显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后取者取胜。因此我们发现了如何取胜的法则:
如果n=(m+1)*k+c,(c为任意自然数,c≤m),那么先取者要拿走c个物品,如果后取者拿走x个(x≤m),那么先取者再拿走(m+1-x)个,结果剩下(m+1)(k-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
所以我们可以知道,如果c=0,即满足n%(m+1)=0,则后手必胜,否则先手必胜。
#include<bits/stdc++.h> using namespace std; int main() { int n,m; while(cin>>n>>m) { if(n%(m+1)==0) cout<<"later win"<<endl; else cout<<"earlier win"<<endl; } return 0; }
假如我们规定最后取光者输,那么结果又会如何呢?
如若满足(n-1)%(m+1)=0,则后手必胜,否则先手必胜。
所以可见其结果并不是简单的相反而已。
②威佐夫博弈(Wythoff Game)
有两堆若干数量的物品,两人轮流从其中一堆取至少一件物品,至多不限,或从两堆中同时取相同件物品,规定最后取完者胜利。
直接说结论了,若两堆物品的初始值分别为n和m,且n>m。
若满足,则后手必胜,否则先手必胜。
ps:左式需先取整!
#include<bits/stdc++.h> using namespace std; int main() { int n,m; while(cin>>n>>m) { if(m>n) swap(n,m); if(floor((n-m)*(1+sqrt(5.0))/2.0)==m) cout<<"later win"<<endl; else cout<<"earlier win"<<endl; } return 0; }
③尼姆博弈(Nimm Game)
有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件,取到最后一件物品的人获胜。
结论:把每堆物品数全部异或起来,即m = a1⊕a2⊕...⊕an,如果得到的值m为0,那么后手必胜,否则先手必胜。
【异或】:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
#include<bits/stdc++.h> using namespace std; int main() { int n,a,m,i; while(cin>>n) { m=0; for(i=0;i<n;i++) { cin>>a; m=m^a; } if(m==0) cout<<"later win"<<endl; else cout<<"earlier win"<<endl; } return 0; }
在尼姆博弈中,又如何计算出第一次所需取走的最少石子数呢?
我的结论如下:
(1) 先记 m = a1⊕a2⊕...⊕an,也就是所有物品堆的总异或值m。
(2) 然后遍历从a1遍历到an,计算 t = a[ i ] - m⊕a[ i ],找到 t>0 且值最小的 t 即可。
还是要注意异或运算的运算顺序是在加减乘除之后的!!!!!
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define MAX 1000005 using namespace std; int a[MAX]; int main() { int i,t,n,min,m; while(cin>>n,n) { min=INF,m=0; for(i=0;i<n;i++) { scanf("%d",&a[i]); m=m^a[i]; } if(m==0) printf("-1\n"); //必败 else { for(i=0;i<n;i++) { t=a[i]-(m^a[i]); if(min>t&&t>0) min=t; } printf("%d\n",min); } } return 0; }
还有一种尼姆博弈的规则是取完者输,那么算法应该更改为:
#include<bits/stdc++.h> using namespace std; int main() { int n; while(cin>>n) { int ans=0,flag=0,x,i; for(i=1;i<=n;i++) { cin>>x; ans^=x; if(x!=1)flag=1; } if(flag) { if(ans==0)cout<<"later win"<<endl; else cout<<"earlier win"<<endl; } else { if(n%2==0)cout<<"earlier win"<<endl; else cout<<"later win"<<endl; } } return 0; }
④斐波那契博弈(Fibonacci Game)
有一堆物品,数量为n个,两人轮流取物品,先手最少取一个,至多无上限,但不能把物品取完,之后每次取的物品数不能超过上一次取的物品数的二倍且至少为一件,取走最后一件物品的人获胜。
首先先了解下什么是斐波那契数列:
斐波那契数列(Fibonacci sequence),指的是这样一个数列:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........
在数学上,斐波纳契数列以如下递归的方法定义:
F(0)=0,F(1)=1,F(n) = F(n-1) + F(n-2)(n>=2,n∈N*)
结论:如果当n是斐波那契数时,后手必胜,否则先手必胜。
#include<bits/stdc++.h> #define MAX 55 using namespace std; int a[MAX]; void Fib() { a[0]=0,a[1]=1; for(int i=2;i<47;i++) a[i]=a[i-1]+a[i-2]; } int main() { Fib(); int n,i,flag; while(cin>>n) { if(n==0)break; flag=0; for(i=0;i<MAX;i++) { if(a[i]==n) { flag=1; break; } } if(flag) cout<<"later win"<<endl; else cout<<"earlier win"<<endl; } return 0; }