【算法与数据结构】—— 博弈论(初级篇之巴什博弈)

博弈论之巴什博弈

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

分析:
显然,如果n=m+1,那么由于一次最多只能取m个物品,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,故后者必然取胜。根据这样的规律,我们发现了如何取胜的法则:
如果n=(m+1)r+s,(r为任意自然数,0≤s≤m),那么先取者首先拿走s个物品
接下来若后取者拿走k(1≤k≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个
以后都保持这样的取法,那么后取者最终会面临(m+1)的局面,那么先取者肯定获胜
总之,要保持给对手留下(m+1)的倍数,最后就一定能获胜

巴什博弈是博弈论中最基础也是最简单的一类博弈问题
下面我们通过一系列题来对这个博弈问题进行更深一步的探究



---入门级题型---



问题描述
Win和Lose两人玩游戏,游戏规则以及要求如下:
1.一共有z局;
2.每局开始都有n个物品;
3.每局都是Win先拿;
4.每次最多可取m个,且m<n;
5.每个人在取东西时总采取当前最优策略;
6.输出获胜人的名字;

输入数据
测试文件有多行,每行为三个数z,n,m,意义如上

输出数据
对于测试文件中的每一行,输出当前获胜者的名字

分析:
本题就是换了一个背景来描述巴什博弈,然后问你对于给定的n和m,输出先手(Win)能否胜利
根据上面对巴什博弈的分析,可以直接给出以下代码:

#include<iostream>
using namespace std;
int main()
{
	int z;cin>>z;
	while(z--)
	{
		int n,m;
		cin>>n>>m;
		if(n%(m+1)==0) 				//如果n==k*(m+1),则先手必败
			cout<<"Lose"<<endl;
		else cout<<"Win"<<endl;
	}
	return 0;
}


---入门级题型---



HDU2149 Public Sale
问题描述
虽然不想,但是现实总归是现实,Lele始终没有逃过退学的命运,因为他没有拿到奖学金。现在等待他的,就是像FarmJohn一样的农田生涯。
要种田得有田才行,Lele听说街上正在举行一场别开生面的拍卖会,拍卖的物品正好就是一块20亩的田地。于是,Lele带上他的全部积蓄,冲往拍卖会。
后来发现,整个拍卖会只有Lele和他的死对头Yueyue。
通过打听,Lele知道这场拍卖的规则是这样的:刚开始底价为0,两个人轮流开始加价,不过每次加价的幅度要在1-N之间,当价格大于或等于田地的成本价 M 时,主办方就把这块田地卖给这次叫价的人。
Lele和Yueyue虽然考试不行,但是对拍卖却十分精通,而且他们两个人都十分想得到这块田地。所以他们每次都是选对自己最有利的方式进行加价。
由于Lele字典序比Yueyue靠前,所以每次都是由Lele先开始加价,请问,第一次加价的时候,
Lele要出多少才能保证自己买得到这块地呢?

输入格式
本题目包含多组测试,请处理到文件结束(EOF)。每组测试占一行。
每组测试包含两个整数M和N(含义见题目描述,0<N,M<1100)

输出格式
对于每组数据,在一行里按递增的顺序输出Lele第一次可以加的价。两个数据之间用空格隔开。
如果Lele在第一次无论如何出价都无法买到这块土地,就输出"none"。

样例输入
4 2
3 2
3 5

样例输出
1
none
3 4 5


本题的考点不再是问先后手谁胜谁负的问题,而是问如果先手能获胜,其第一次的叫价应该为多少
当 m>n 的时候其实就是前面那道题的常规情形,我们只需要判断此时 m%(n+1) 是否等于0,如果是则先手输,直接输出"none"即可;如果不是,则通过公式 (m-i)%(n+1)==0 即能求出先手第一次应该叫的价(设叫价为i)
而当出现 m<n 的时候,此时第一次定价落在区间[m,n]之间都是可以的
下面直接给出本题的完整代码:

#include<iostream>
using namespace std;
int main()
{
	int m,n;
	while(cin>>m>>n){
		if(m%(n+1)==0) cout<<"none";
		else if(m<=n){
			for(int i=m;i<=n;i++)
			{
				if(i!=m) cout<<" ";
				cout<<i;
			}
		}else{
			for(int i=1;i<=n;i++)
				if((m-i)%(n+1)==0){
					cout<<i;
					break;
				}
		}
		cout<<endl;	
	}
	return 0;
}


---进阶级题型---



HDU1847 Good Luck in CET-4 Everybody!
问题描述
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。

Good luck in CET-4 everybody!

输入数据
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。

输出数据
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。

样例输入
1
3

样例输出
Kiki
Cici


分析:
首先我们要想到,在面对当前牌数为3的时候,对先手而言是必败局,因为此时无论拿多少都会输
这是一个很关键的地方,我们试着把牌数替换为3的倍数来看看:
比如当前牌数为6,先手无论是拿2还是4哪一个,后手都能以4或者2来应对以赢得比赛
而如果先手拿1,那后手就拿2,此时牌数剩下3,回到牌数为3的情形,故先手还是输;
又比如当前牌数为9,先手若拿1或者8,后手均能以8或1来应对以赢得比赛
而如果先手拿2或者4,那后手就拿4或者2,从而使得牌数剩下3,回到牌数为3的情形,故先手还是输;
……
假设当前牌数为3*n,无论先手拿多少(假设拿了a),后手都可以通过拿b(要求(a+b)%3=0)来使得剩下的牌数始终为3的倍数,最终将牌数逼近3,从而赢得最终的胜利

因此为了能赢得比赛,那么就要尽量造成这样的局势给对方
因为任何不是3的倍数的数加1或2都可以变成3的倍数(同理减1或2也可以变成3的倍数)
也就是说假设目前的个数不是3的倍数,那我肯定能把它拿成3的倍数,比如现在是11个,那我拿走2个就变成9,这样就造成对方为3的倍数局势,那么对方拿m个我都可以通过拿1或者2使总共一轮拿的数目成为3的倍数,这样就会有两种情况:
1.刚好拿完
2.剩下的还有3的倍数个,那继续上述操作
所以这样下去的最终结局是,必胜

下面直接给出本题的完整代码:

#include<iostream>
using namespace std;
int main()
{
	int n;
	while(cin>>n){
		if(n%3==0) cout<<"Cici"<<endl;
		else cout<<"Kiki"<<endl;
	}
	return 0;
}


---进阶级题型---



HDU2147 kiki’s game
问题描述
最近,kiki非常闲。在她无聊的时候,她的脑海里浮现出一个主意,玩棋盘游戏。棋盘的大小是n * m。首先,在右上角放一个硬币(1,m)。每当有人将硬币移到左侧、下方或左下方的空白区域时,下一个无法移动的人都会输掉比赛。kiki使用回合制进行游戏。游戏始终从kiki开始。如果双方都表现出色,谁将赢得比赛?

输入数据
输入包含多个测试用例。每行包含两个整数n,m(0 <n,m <= 2000)。当n = 0和m = 0时终止输入。

输出数据
如果kiki胜利则输出“Wonderful!”,否则输出“What a pity!”。

样例输入
5 3
5 4
6 6
0 0

样例输出
What a pity!
Wonderful!
Wonderful!



分析:
这道题也是巴什博弈的一个变形,它和HDU1847有点类似(找规律),我们只需要分析一下前面的几个情况就能大致找出本题的规律,从而解答本题。
在进行下面的情况举例分析时,同学们需要注意的是,对于先手而言,能否取得胜利其实是指先手在给出的地图中能否找到一个必胜之路,这是一个存在问题,而非任意(全部)。因此在举例的过程中,对于后面较大规格的枚举中,我们只需要找到一条必胜之路即可
① 首先是n=m=1的情形(如下),此时先手无路可走,必输

n=m=1


② 当n=m=2时情况如下:

n=m=2
显然此时先手可以直接从起点出发到终点,故先手必胜



③ 当n=2,m=3时情况如下:

n=2,m=3
若先手走格点1,那么局势就变成了情况②中的情形,先手必输
若先手走格点2,那后手就直接走到终点,先手输
若先手走格点3,那么接下来后手就只能走格点2,最后先手走到终点,先手胜
综上,在n=2,m=3的情况下,先手能够找到一条必胜之路(第一步走格点3)



④ 当n=m=3时情况如下:

n=m=3
若先手走的第一步是到格点2,那么在后手的回合时其可以直接从格点2出发到终点。故先手输
若先手走的第一步是到格点1,那么后手为了能赢得比赛,其就不能直接从格点1走到格点5,那样在先手的回合时先手就会直接从格点5到终点从而赢得比赛。因此后手会从格点1走到格点4。接下来到先手的回合,先手只能由格点4走向格点5。最后后手由格点5走向终点,后手胜利。还是先手输
而对于3 ╳ 3规格的地图,从起点(右上角)出发,其往格点1走和往格点3走其实是对称的。换言之先手若先走格点3,其结果和先走格点1一样,必输
综上所述,在n=m=3的情况下,先手没有任何一条路线能够取得胜利,其必输



⑤ 当n=3,m=4时情况如下:

n=3,m=4
若先手走的第一步是格点1,那么此时情况就变成了情况④中的情形(但先后手易位)
因此对于后手而言,无论他采取怎样的行走方案,其都必输,故先手胜利
但是若先手的第一步走的是格点2或者格点3,后手为了胜利其都将走格点4,这样在又回到先手的回合时,其只能走格点5,最终后手再由格点5走到终点,赢得胜利,故先手输
因此当n=3,m=4时,先手能够找到一条必胜之路(第一步走格点1)



⑥ 当n=m=4时情况如下:

n=m=4
为了胜利,先手可以直接由起点走到格点2,使局势变为情况④中的情形(但先后手易位),从而赢得最终的胜利
但若先手走的格点1(或者格点3),此时后手为了胜利就直接由格点1(或者格点3) 走到格点2,使局势变为情况④中的情形(但此时先后手未易位),故先手必输
因此在4 ╳ 4规格的图中,先手还是能找到一条必胜之路(第一步走格点2)



⑦ 当n=4,m=5时情况如下:
n=4,m=5
若先手走的第一步是格点1,那么局势就变成了情况⑥中的情形,后手就可以通过⑥中的必胜方案来行走以赢得比赛,故先手不能走格点1
若先手走的第一步是格点2,那么后手可以直接从格点2走到格点4,使先手的局势变为情况④中的情形,先手必输。故先手也不能走格点2
但若先手走的第一步是格点3:假设后手走格点5,那么先手就可以走格点6,接下来在整个最下面一行,先后手只能交替前进,那么最终先手会获胜;再假设后手在格点3未走格点5而是走的格点0,那么先手就直接由格点0走到格点8,此后前后手又是交替前进,显然最终还是先手获胜
因此在4 ╳ 5规格的图中,先手同样能找到一条必胜之路(第一步走格点3)



⑧ 当n=m=5时情况如下:
n=m=5
显然此时的情况就是在⑦的基础上多了一行,那么此时在⑦中的必胜之路(先走格点3)就变成了在⑦中的先走格点1(对称性)的情形。故在这种情况下,先手将再也找不到任何的必胜之路,必输




综和①-⑦,不难发现:当m或n中存在一个数是偶数时,先手总能找到一条必胜之路
出现这样的现象的原因是:“至左下方空白区域”这一操作使在选择路线时会更灵活,当先手在偶数行(列)行进时,其能够利用这一操作时刻对后手的选择进行调整,使得局势总偏向先手胜利。
下面直接给出本题的完整代码:

#include<iostream>
using namespace std;
int main()
{
	int n,m;
	while(cin>>n>>m)
	{
		if(n==0 && m==0) break;
		if(n%2== 0 || m%2==0) cout<<"Wonderful!"<<endl;
		else cout<<"What a pity!"<<endl;
	}
	return 0;
} 


---进阶级题型---



HDU2897 邂逅明下
问题描述
小t和所有世俗的人们一样,期待那百年难遇的日食。驻足街头看天,看日月渐渐走近,小t的脖子那个酸呀(他坚持这个姿势已经有半个多小时啦)。他低下仰起的头,环顾四周。忽然发现身边竟站着位漂亮的mm。天渐渐暗下,这mm在这街头竟然如此耀眼,她是天使吗?
小t对mm惊呼:“缘分呐”。mm却毫不含糊:“是啊,500年一遇哦!”(此后省略5000字….)
小t赶紧向mm要联系方式,可mm说:“我和你玩个游戏吧,赢了,我就把我的手机号告诉你。”小t,心想天下哪有题目能难倒我呢,便满口答应下来。mm开始说游戏规则:“我有一堆硬币,一共7枚,从这个硬币堆里取硬币,一次最少取2枚,最多4枚,如果最后剩下的硬币少于2枚就要一次取完。我和你轮流取,直到堆里的硬币取完,最后一次取硬币的算输。我玩过这个游戏好多次了,就让让你,让你先取吧”
小t掐指一算,不对呀,这是不可能的任务么。小t露出得意的笑:“还是mm优先啦,呵呵”。
mm霎时愣住了,想是对小t的反应出乎意料吧。
她却也不生气:“好小子,挺聪明呢,要不这样吧,你把我的邮箱给我,我给你发个文本,每行有三个数字n,p,q,表示一堆硬币一共有n枚,从这个硬币堆里取硬币,一次最少取p枚,最多q枚,如果剩下少于p枚就要一次取完。两人轮流取,直到堆里的硬币取完,最后一次取硬币的算输。对于每一行的三个数字,给出先取的人是否有必胜策略,如果有回答WIN,否则回答LOST。你把对应的答案发给我,如果你能在今天晚上8点以前发给我正确答案,或许我们明天下午可以再见。”
小t二话没说,将自己的邮箱给了mm。当他兴冲冲得赶回家,上网看邮箱,哇!mm的邮件已经到了。他发现文本长达100000行,每行的三个数字都很大,但是都是不超过65536的整数。小t看表已经下午6点了,要想手工算出所有结果,看来是不可能了。你能帮帮他,让他再见到那个mm吗?

输入数据
不超过100000行,每行三个正整数n,p,q(意义如上)

输出数据
对应每行输入,按前面介绍的游戏规则,判断先取者是否有必胜策略。输出WIN或者LOST

样例输入
7 2 4
6 2 4

样例输出
LOST
WIN


分析:
前面看到的关于巴什博弈的题大多数都是取到或达到最后值的人获胜
本题却是最后取硬币的人输,但是从本质上来看都是一样的
首先要知道,如果n%(p+q)==0,那么先手必胜。策略是先手第一次取q个,之后每一轮假设后手取i个,那么先手就取p+q-i个。这样一来每轮都在硬币堆中取了p+q个,那么最后一轮必然是后手取p个;
如果n%(p+q)=k <= p,那么无论先手第一次取多少(设为i),后手都取p+q-i,则最后必然是先手取k个;
如果n%(p+q)=k > p,那么先手第一次取j个,使得(k-j)%(p+q)<p,则此时就变成了上一种情况,但是先后手发生了交换,故依然是先手胜
下面直接给出本题的完整代码:

#include<iostream>
using namespace std;
int main()
{
	int n,p,q;
	while(cin>>n>>p>>q){
		if(n%(p+q)==0) cout<<"WIN"<<endl;
		else if(n%(p+q)<=p) cout<<"LOST"<<endl;
		else cout<<"WIN"<<endl;
	}
	return 0;
}
发布了38 篇原创文章 · 获赞 75 · 访问量 9521

猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/104311580