博弈入门:
(一)巴什博弈:
理论:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个.最后取光者得胜.若n%(m+1)==0,则先手必败,否则先手必胜。
显然,如果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个,若n%(m+1)==0 则后手胜 ,反之先手胜利
例题:
1.hdu1846 Brave Game
#include<bits/stdc++.h>
using namespace std;
int c,n,m;
int main() {
cin>>c;
while(c--) {
cin>>n>>m;
int mod=n%(m+1);
if(mod) cout<<"first"<<endl;
else cout<<"second"<<endl;
}
return 0;
}
2.hdu 2149 Public Sale
#include<bits/stdc++.h>
using namespace std;
int m,n;
int main() {
while(cin>>m>>n) {
if(n>=m) {
for(int i=m; i<=n-1; ++i) {
cout<<i<<' ';
}
cout<<n<<endl;
} else {
int mod=m%(n+1);
if(mod) {
cout<<mod<<endl;
} else {
cout<<"none"<<endl;
}
}
}
return 0;
}
3.hdu 2147 kiki's game
PN图打表分析
#include<bits/stdc++.h>
using namespace std;
int n,m;
int main(){
while(cin>>n>>m &&n&&m){
if(n%2==0 || m%2==0){
printf("Wonderful!\n");
}else{
printf("What a pity!\n");
}
}
return 0;
}
(二)斐波那契博弈
理论:出门左转 传送门
结论:有一堆石子,两个顶尖聪明的人玩游戏,先取者可以取走任意多个,但不能全取完,以后每人取的石子数不能超过上个人的两倍,当石子数为斐波那契数 ,先手输,反之先手赢。
例题:
hdu 2516 取石子游戏
//当石子的数量为斐波那契数时,先手必败
//反之,先手赢
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
int fib[50];
fib[0]=2,fib[1]=3;
for(int i=2;i<50;++i){
fib[i]=fib[i-1]+fib[i-2];
}
while(cin>>n&&n){
bool flag=0;
for(int i=0;i<50;++i){
if(n==fib[i]){
flag=1;
cout<<"Second win"<<endl;
}
if(fib[i]>n){
break;
}
}
if(!flag){
cout<<"First win"<<endl;
}
}
return 0;
}
(三)威佐夫博弈
理论:出门左转 传送门
结论:有两堆各若干个物品,两个人轮流从任意一堆中取出至少一个或者同时从两堆中取出同样多的物品,规定每次至少取一个,至多不限,最后取光者胜利。两堆物品a,b , c=floor((b-a)*((sqrt(5.0)+1)/2)); 若a==c则后手赢,反之先手赢
例题:
hdu 1527 取石子游戏
//若符合奇异局势,则先手输,否则先手胜
#include<math.h>
#include<iostream>
using namespace std;
int main(){
int a,b,c;
while(cin>>a>>b){
if(a>b) swap(a,b);
c=floor((b-a)*((sqrt(5.0)+1)/2));
if(a==c){
cout<<0<<endl;
}
else cout<<1<<endl;
}
return 0;
}
(四)尼姆博弈(和SG函数有关)
理论:来吧,还是出门左转 传送门
结论:有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件,取到最后一件物品的人获胜。 结论就是:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败,否则先手必胜。
重点:
SG函数算是博弈里最重要的部分了,菜鸡经过几天的学习也算是略有了解 ,不是太清楚的可以搜大佬的博客了解一下(等自己对SG有自己理解时再来更新
总结:无一例外要算sg函数,如果游戏的sg值为0(即各个点的异或和为0),先手必败,反之先手胜利
例题:
hdu 1850 Being a Good Boy in Spring Festival
#include<bits/stdc++.h>
using namespace std;
int m;
int a[105];
int main(){
while(cin>>m&&m){
int ans=0,cnt=0;
for(int i=0;i<m;++i){
cin>>a[i];
ans^=a[i];
}
if(ans==0){
cout<<0<<endl;
}
else{
for(int i=0;i<m;++i){
int k=ans^a[i];
if(k<a[i]){
cnt++;
}
}
cout<<cnt<<endl;
}
}
return 0;
}
hdu 1847 Good Luck in CET-4 Everybody!
#include<bits/stdc++.h>
using namespace std;
int n,a[15],sg[1005];
int mex(int x) {
if(sg[x] != -1) {
return sg[x];
}
bool vis[1005];
for(int i=0; i<1005; ++i)
vis[i]=false;
for(int i=0; i<=10; ++i) {
int temp=x-a[i];
if(temp<0) break;
sg[temp]=mex(temp);
vis[sg[temp]]=true;
}
for(int i=0;; ++i) {
if(!vis[i]) {
sg[x]=i;
break;
}
}
return sg[x];
}
int main() {
a[0]=1;
for(int i=1; i<=10; ++i) {
a[i]=a[i-1]*2;
}
while(cin>>n) {
memset(sg,-1,sizeof(sg));
if(mex(n)) {
cout<<"Kiki"<<endl;
} else {
cout<<"Cici"<<endl;
}
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
while(~scanf("%d",&n)) {
if(n%3==0)
printf("Cici\n");
else
printf("Kiki\n");
}
return 0;
}
poj 2960 S-Nim
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int s[200],sg[10020],vis[20000];
int k,m;
int l,h[200];
void getSg(int n) {
sg[0] = 0;//主要是让终止状态的sg为0
memset(vis, -1, sizeof(vis));
for(int i = 1; i <=n ; i++) {
for(int j = 1; j <=k && s[j] <= i; j++) {
vis[sg[i-s[j]]]=i;//将所有后继的sg标记为i,然后找到后继的sg没有出现过的最小正整数
//优化:注意这儿是标记成了i,刚开始标记成了1,这样每次需初始化mk,而标记成i就不需要了
}
int j = 0;
while(vis[j] == i) j++;
sg[i] = j;
}
}
int main() {
while(scanf("%d",&k)!=EOF && k) {
for(int i=1; i<=k; ++i) {
scanf("%d",s+i);
}
sort(s+1,s+1+k);
getSg(10005);
scanf("%d",&m);
while(m--) {
scanf("%d",&l);
int ans=0;
for(int i=1; i<=l; ++i) {
scanf("%d",h+i);
ans^=sg[h[i]];
}
if(ans) {
cout<<"W";
} else cout<<"L";
}
cout<<endl;
}
return 0;
}
luogu 1290 欧几里德的游戏
#include<bits/stdc++.h>
using namespace std;
int T, m, n;
bool solve(int n, int m) {
if (!m)return false;
if (n/m == 1)return !solve(m, n%m);
else return true;
}
int main() {
scanf("%d", &T);
for (int i = 1; i <= T; i++) {
scanf("%d%d", &n, &m);
if (solve(max(n, m), min(n, m)))
printf("Stan wins\n");
else
printf("Ollie wins\n");
}
}