几种简单的博弈 1

1.巴什博奕:


??????(是我老了还是我老了)

题目大意:

两个顶尖聪明的人在玩游戏,有n个石子,每人可以随便拿1~m个石子,不能拿的人为败者,问谁会胜利

分析:

首先我们可以想到,假设有<m个物品,那么先手直接取光所有的石子,即先手必胜;

如果有m+1个物品,那么无论先手取几个物品,很遗憾的是后手都可以将先手剩下的物品取光,则先手必败;

如果物品数k在m+2~2m之间,那么先手只需要先拿走k-m-1个物品,就将后手置于了有m+1个物品的必败态,因此此时先手必胜;

那对于n>2m的情况,我们怎么思考?

可以想到,当拿物品时,只要后手置于m+1个物品,那么后手必败。

因此,可以计算n关于m+1的表达式:n=k(m+1)+r,r<m+1;

若r!=0,那么先手在第一次拿的时候拿走r个物品,接下来后手拿k个物品,那么先手只要保持自己拿的物品数h+k=m+1,就一定可以胜利!

当r=0时,假设先手拿h个,那么后手一定会拿m+1-h个,则先手必败;

例题:

HDU 1846 Brave Game

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}
int T,n,m;

int main(){
	T=read();
	while(T--) {
		n=read();
		m=read();
		if(n%(m+1)==0) printf("second\n");
		else printf("first\n");
	}
	return 0;
}

HDU 4764 Stone

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}
int T,n,m;

int main(){
	while(1) {
		n=read();
		m=read();
		if(n==0&&m==0) break;
		if((n-1)%(m+1)==0) printf("Jiang\n");
		else printf("Tang\n");
	}
	return 0;
}

HDU 1847 Good Luck in CET-4 Everybody!

这……

#include<bits/stdc++.h>

using namespace std;

int n;

int main() {
	while(cin>>n) {
		if(n%3==0) printf("Cici\n");
		else printf("Kiki\n");
	}
	return 0;
}

2.威佐夫博弈:

题目大意

有两堆石子,两个\(\color{#66ccff}{顶尖聪明}\)的人在玩游戏,每次每个人可以从任意一堆石子中取任意多的石子或者从两堆石子中取同样多的石子,不能取得人输,分析谁会获得胜利

分析

并不会详细的证明.jpg

打表找规律:

我们用(a,b)来表示两堆石子的数量(为简便设a<=b)

那么先手必败有:

(0,0),(1,2),(3,5),(4,7),(6,10),(8,13),(9,15),(11,18)

顺带提一句:这些局势被称为“\(\color{red}{奇异局势}\)

然后你会发现:(int)(a与b的差值*1.618)=a(显然不是我发现的

所以当abs(a-b)*1.618=a时,先手必败;

而实际上这个1.618就是\(\frac{ \sqrt{5}+1}{2}\)

在精度要求较高的题目中,1.618常写作(sqrt(5.0)+1.0)/2.0;

例题

HDU 1527 取石子游戏

标准威佐夫博弈,毫无思考含量

#include<bits/stdc++.h>

using namespace std;

int n,m;

int main() {
	while(cin>>n>>m) {
		double d=fabs(m-n);
		int ans=(sqrt(5.0)+1.0)/2.0*d;
		if(ans==min(n,m)) 
			printf("0\n");
		else 
			printf("1\n");
	}
	return 0;
}

求解第一次应该如何取:

依据上方思路暴力枚举

显然有两种方法:

  1. 两堆石子取相同的个数:这个时候差值是不变的,因此不需要再次计算差值* 1.618(就好写,不写的那么复杂了),我们只需要枚举两堆同时减去多少个石子,比较和(int)(差值*1.618)是否相等(相等输出(显然枚举范围是1~min(n,m))
  2. 取其中的一堆石子,然后计算比较

HDU 2177 取(2堆)石子游戏

(这个好像应该是较大较小的都要尝试取,但可能因为数据太水就过了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>

using namespace std;

int n,m;

int main(){
	while(1) {
		scanf("%d%d",&n,&m);
		if(n==0&&m==0) break;
		if(n>m) swap(n,m);
		double d=fabs(m-n);
		int ans=((sqrt(5.0)+1)/2.0)*d;
		if(ans==n) 
			printf("0\n");
		else {
			printf("1\n");
			for(int i=1,n1,m1;i<=n;i++) {
				n1=n-i;m1=m-i;
				if(n1==ans)
					printf("%d %d\n",n1,m1);
			}
			for(int i=m-1,p,q;i>=0;i--) {
				p=n;q=i;
				if(p>q) swap(p,q);
				double c=fabs(q-p);
				int Ans=((sqrt(5.0)+1.0)/2.0)*c;
				if(Ans==p) 
					printf("%d %d\n",p,q);
			}
		}
	}
	return 0;
}
posted @ 2020-04-25 18:39  Sweetness  阅读(314)  评论(1编辑  收藏  举报