博弈论总结

算博弈题,一定要算到一个周期取胜。

巴什博弈

巴什博弈:
 只有一堆n个物品,两个人轮流从这堆物品中取物, 规定每次至少取一个,最多取m个。最后取光者得胜。

策略:
 如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此如果最后取光者胜,那么当n%(m+1)==0时,后手胜。
 如果最后取光者输,(n-1)%(m+1)==0时,后手胜利。

eg 1.Good Luck in CET-4 Everybody!
 代码:

#include <bits/stdc++.h>
using namespace std;
int main(){
	int n;
	while(scanf("%d",&n)!=EOF){
		if(n%3==0) puts("Cici");
		else puts("Kiki");
	}
	return 0;
}

eg2.kiki’s game

组合博弈,PN图:
必败点(P点):前一个选手将取胜的位置。 必败点N点:下一个选手将取胜的位置。 特点:
(1)所有终结点都是必败点(P点);
(2)从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点);
(3)从必败点(P点)都只能进入必胜点。

步骤:
(1)将所有终结位置标记为P点; (2)将所有一步操作能进入P点的位置标记为N点;
(3)如果从某个点开始的所有操作能只能进入N点,则将其标记为P点; (4)如果在步骤3未找到新的P点,则算法终止,否则,返回步骤2.

画图规则:
画图时,选定对角线定点里面的那个点为末状态所在点,根据最开始的那一列确定是P点还是N点。
(1)每个图的末状态均为P点;
(2)所有能够一步到达P点的都是N点;
(3)所有能够一步到达N点的都是P点。

P N P N P N P N
N N N N N N N N
P N P N P N P N
N N N N N N N N
P N P N P N P N
N N N N N N N N
P N P N P N P N
从中可以看出当行列均为奇数的时候,一定为必败点。
因此,当行数或者列数为偶数的时候先手赢。
当n为偶数或者m为偶数时,先手必赢。否则,后手必赢。

代码:

#include <bits/stdc++.h>
using namespace std;
int main(){
	int n,m;
	while(scanf("%d%d",&n,&m)&&!(n==0&&m==0)){
		if(n%2==0||m%2==0) puts("Wonderful!");
		else puts("What a pity!");
	}
	return 0;
}

eg3.Public Sale
思路:
首先,一看每次加价幅度在1~N之间,可知此为巴什博弈。
当n%(m+1)==0时,后手必赢,输出none、
此题还要求输出可以加的数字,分情况讨论:
(1)n<=m时,那么就可以从n加到m,输出这之间的数字。
(2)n>m时,如果n%(m+1)==0,那么后手必胜。否则,先手就加这个余数,使之变成n%(m+1)==0。

代码:

#include <bits/stdc++.h>
using namespace std;
int m,n;
int main(){
	while(scanf("%d%d",&m,&n)!=EOF){
		if(m<=n){//成本价m<=最大幅度n,一次出价就可以取胜,可以加的为m~n之间的数字 
			for(int i=m;i<=n;i++){
				if(i==m) printf("%d",i);
				else printf(" %d",i);
			}
		}
		else{//m>n时,如果m%(n+1)==0,那么后手必胜。否则,先手就加这个余数使之满足m%(n+1)==0 
			int x=m%(n+1);
			if(x==0) printf("none");
			else printf("%d",x);
		}
		printf("\n");
	}
	return 0;
}

eg4.悼念512汶川大地震遇难同胞——选拔志愿者
思路:
和上一道题一样。n<=m时,先手胜。n>m时,当n%(m+1)==0时,后手胜,否则先手胜。

代码:

#include <bits/stdc++.h>
using namespace std;
int m,n;
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		if(n<=m){//如果n<=m,那么先手胜 
			puts("Grass");
		}
		else{//n>m时,如果n%(m+1)==0,那么后手必胜。否则,先手就加这个余数使之满足m%(n+1)==0,先手胜 
			int x=n%(m+1);
			if(x==0) puts("Rabbit");
			else puts("Grass");
		}
	}
	return 0;
}

eg5. 邂逅明下
巴什博弈的简易变形。
一般是取的范围为1~m,用n%(m+1)==0表示后手必胜。(当规定最后一次取的人赢时)
此题取的范围为p~q,那么就用n%(p+q)。
因为最后一次取硬币的输掉,因此当n%(p+q)==0时,先手必胜。
当n%(p+q)!=0时,设余数x=n%(p+q),
那么当x<=p时,先手输。
否则,先手赢。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,p,q;

int main(){
	while(scanf("%lld%lld%lld",&n,&p,&q)!=EOF){
		if(n%(p+q)==0) puts("WIN");
		else{
			int x=n%(p+q);
			if(x<=p) puts("LOST");
			else puts("WIN");
		}
	}
	return 0;
} 

威佐夫博弈

威佐夫博弈:
有两堆各若干个物品,两个人轮流从任意一堆中取出至少一个或者同时从两堆中取出同样多的物品,规定每次至少取一个,至多不限,最后取光者胜利。

先手必输局势:奇异局势
(0,0),(1,2),(3,5),(4,7),(6,10),(8,13),(9,15),(11,18),(11,18)
(a[k],b[k])
发现是0,1,2,3,4,5,,n 这些局势的第一个值是前面未出现过的最小的自然数,第二个值是第一个值+k, 第一个(0,0)的k=0,第二个(1,2)的k=1,依次递增。
a[k]=(int)((b[k]-a[k])*1.618) 。
0.618是黄金分割率,而威佐夫博弈正好是1.618= (sqrt(5)+1)/2。

威佐夫博弈步骤:
(1)先求出1.618
double x= (sqrt(5)+1)/2;
(2)输入a,b,
(3)求出mi=min(a,b),ma=max(a,b);
(4)int c=(int)((ma-mi)*x);
如果c==最小值mi,先手必输。

eg.取石子游戏

按模板解即可

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double x=(sqrt(5)+1)/2;//威佐夫博弈的分割率 
int a,b;

int main(){
	while(scanf("%d%d",&a,&b)!=EOF){
		int ma=max(a,b),mi=min(a,b);
		int c=ma-mi;
		c=(int)(c*x);
		if(c==mi) puts("0"); //先手输
		else puts("1");
	}
	return 0;
} 

尼姆博弈(Nimm Game)

Nim博弈:
有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件, 取到最后一件物品的人获胜。

做法:
将所有物品数量异或起来,如果等于0,那么先手必败,否则先手必胜。

eg.取(m堆)石子游戏

思路:
先求所有石子数量异或和,如果等于0,那么先手必败。
由于此题在先手必胜的时候还要输出先手第1次取石子的所有方法,因此需要用到奇异局势。
用(a,b,c)表示某种局势,首先(0,0,0)显然是奇异局势,无论谁面对奇异局势,都必然失败。任何奇异局势(a,b,c)都有a⊕b⊕c =0。
所以从一个非奇异局势向一个奇异局势转换的方式可以是:
1)使 a = c⊕b
2)使 b = a⊕c
3)使 c = a⊕b
对于每一个非奇异局势(a,b,c)(a<b<c),想要变成奇异局势,只需要将c变成a^b即可。因为a ^ b ^ (a ^ b)=0。
因此只需要找满足(s^a[i])<s[i]的即可,输出a[i]和(s ^ a[i])。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+10;
int m,a[N];
int main(){
	while(scanf("%d",&m)&&m){
		int s=0;
		for(int i=1;i<=m;i++){
			scanf("%d",&a[i]);
			s^=a[i];
		}
		if(s==0) puts("No");//异或和为0,先手必输 
		else{
			puts("Yes");
			for(int i=1;i<=m;i++){
				if((s^a[i])<a[i]){//涉及异或一定一定要打括号!!! 
					printf("%d %d\n",a[i],s^a[i]);
				}
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值