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

博弈论之巴什博弈

巴什博弈(Bash Game)

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

分析

显然,如果 n = m + 1 n=m+1 n=m+1,那么由于一次最多只能取 m m m 个物品,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,故后者必然取胜。根据这样的规律,我们发现了如何取胜的法则。

如果 n = ( m + 1 ) r + s n=(m+1)r+s n=(m+1)r+s,( r r r为任意自然数, 0 ≤ s ≤ m 0≤s≤m 0sm),那么先取者首先拿走 s s s 个物品,接下来若后取者拿走 k ( 1 ≤ k ≤ m ) k(1≤k≤m) k(1km) 个,那么先取者再拿走 m + 1 − k m+1-k m+1k 个,结果剩下 ( m + 1 ) × ( r − 1 ) (m+1)\times(r-1) (m+1)×(r1)个,以后都保持这样的取法,那么后取者最终会面临 ( m + 1 ) (m+1) (m+1) 的局面,而先取者则必然获胜。总之,要保持给对手留下 ( m + 1 ) (m+1) (m+1) 的倍数,最后就一定能获胜。

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



—— 入门级题型 ——


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

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

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

分析:
本题就是换了一个背景来描述巴什博弈,然后问你对于给定的 n n n m m 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 始终没有逃过退学的命运,因为他没有拿到奖学金。现在等待他的,就是像 Farm John 一样的农田生涯。

要种田得有田才行,Lele 听说街上正在举行一场别开生面的拍卖会,拍卖的物品正好就是一块 20 亩的田地。于是,Lele 带上他的全部积蓄,冲往拍卖会。

后来发现,整个拍卖会只有 Lele 和他的死对头Yueyue。

通过打听,Lele 知道这场拍卖的规则是这样的:刚开始底价为 0,两个人轮流开始加价,不过每次加价的幅度要在 1 − N 1-N 1N 之间,当价格大于或等于田地的成本价 M M M 时,主办方就把这块田地卖给这次叫价的人。

Lele 和 Yueyue 虽然考试不行,但是对拍卖却十分精通,而且他们两个人都十分想得到这块田地。所以他们每次都是选对自己最有利的方式进行加价。

由于 Lele 字典序比 Yueyue 靠前,所以每次都是由 Lele 先开始加价,请问,第一次加价的时候,Lele 要出多少才能保证自己买得到这块地呢?

输入格式

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

输出格式

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

样例输入

4 2
3 2
3 5

样例输出

1
none
3 4 5


本题的考点不再是问先后手谁胜谁负的问题,而是问如果先手能获胜,其第一次的叫价应该为多少。

m > n m>n m>n 的时候其实就是前面那道题的常规情形,我们只需要判断此时 m % ( n + 1 ) m\%(n+1) m%(n+1) 是否等于 0,如果是则先手输,直接输出 “none” 即可;如果不是,则通过公式 ( m − i ) % ( n + 1 ) = = 0 (m-i)\%(n+1)==0 (mi)%(n+1)==0 即能求出先手第一次应该叫的价(设叫价为 i i i),而当出现 m < n m<n 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 n 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 ) n(1<=n<=1000) 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 3n 3n,无论先手拿多少(假设拿了 a a a),后手都可以通过拿 b b b(要求 ( a + b ) % 3 = 0 (a+b)\%3=0 (a+b)%3=0)来使得剩下的牌数始终为 3 的倍数,最终将牌数逼近 3,从而赢得最终的胜利。

因此为了能赢得比赛,那么就要尽量造成这样的局势给对方。因为任何不是 3 的倍数的数加 1 或 2 都可以变成 3 的倍数(同理减 1 或 2 也可以变成 3 的倍数)。也就是说假设目前的个数不是 3 的倍数,那我肯定能把它拿成 3 的倍数,比如现在是 11 个,那我拿走 2 个就变成 9,这样就造成对方为 3 的倍数局势,那么对方拿 m m 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 n\times m n×m。首先,在右上角放一个硬币 ( 1 , m ) (1, m) (1,m)。每当有人将硬币移到左侧、下方或左下方的空白区域时,下一个无法移动的人都会输掉比赛。kiki 使用回合制进行游戏。游戏始终从 kiki 开始。如果双方都表现出色,谁将赢得比赛?

输入数据

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

输出数据

如果 kiki 胜利则输出 Wonderful!,否则输出What a pity!

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

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



这道题也是巴什博弈的一个变形,它和 HDU1847 有点类似(找规律),我们只需要分析一下前面的几个情况就能大致找出本题的规律,从而解答本题。

在进行下面的情况举例分析时,同学们需要注意的是,对于先手而言,能否取得胜利其实是指先手在给出的地图中能否找到一个必胜之路,这是一个存在问题,而非任意(全部)。因此在举例的过程中,对于后面较大规格的枚举中,我们只需要找到一条必胜之路即可:

  1. 首先是 n = m = 1 n=m=1 n=m=1 的情形(如下),此时先手无路可走,必输。

    n=m=1

  2. n = m = 2 n=m=2 n=m=2 时情况如下:

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

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

  4. n = m = 3 n=m=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=m=3 n=m=3 的情况下,先手没有任何一条路线能够取得胜利,其必输。

  5. n = 3 , m = 4 n=3,m=4 n=3m=4 时情况如下:

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

  6. n = m = 4 n=m=4 n=m=4 时情况如下:

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

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

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



综和 1 ∼ 8 1\sim 8 18,不难发现:当 m m m n n 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 t 和所有世俗的人们一样,期待那百年难遇的日食。驻足街头看天,看日月渐渐走近,小t的脖子那个酸呀(他坚持这个姿势已经有半个多小时啦)。他低下仰起的头,环顾四周。忽然发现身边竟站着位漂亮的 m m mm mm。天渐渐暗下,这 m m mm mm 在这街头竟然如此耀眼,她是天使吗?

t t t m m mm mm 惊呼:“缘分呐”。 m m mm mm 却毫不含糊:“是啊,500年一遇哦!”(此后省略5000字….)

t t t 赶紧向 m m mm mm 要联系方式,可 m m mm mm 说:“我和你玩个游戏吧,赢了,我就把我的手机号告诉你。”小 t t t,心想天下哪有题目能难倒我呢,便满口答应下来。 m m mm mm开始说游戏规则:“我有一堆硬币,一共 7 枚,从这个硬币堆里取硬币,一次最少取 2 枚,最多 4 枚,如果最后剩下的硬币少于2枚就要一次取完。我和你轮流取,直到堆里的硬币取完,最后一次取硬币的算输。我玩过这个游戏好多次了,就让让你,让你先取吧”。

t t t 掐指一算,不对呀,这是不可能的任务么。小t露出得意的笑:“还是 m m mm mm优先啦,呵呵”。

m m mm mm 霎时愣住了,想是对小 t t t 的反应出乎意料吧。

她却也不生气:“好小子,挺聪明呢,要不这样吧,你把我的邮箱给我,我给你发个文本,每行有三个数字 n , p , q n,p,q npq,表示一堆硬币一共有 n n n 枚,从这个硬币堆里取硬币,一次最少取 p p p 枚,最多 q q q 枚,如果剩下少于 p p p 枚就要一次取完。两人轮流取,直到堆里的硬币取完,最后一次取硬币的算输。对于每一行的三个数字,给出先取的人是否有必胜策略,如果有回答 WIN,否则回答LOST。你把对应的答案发给我,如果你能在今天晚上 8 点以前发给我正确答案,或许我们明天下午可以再见。”

t t t 二话没说,将自己的邮箱给了 m m mm mm。当他兴冲冲得赶回家,上网看邮箱,哇! m m mm mm的邮件已经到了。他发现文本长达 100000 行,每行的三个数字都很大,但是都是不超过 65536 的整数。小t看表已经下午 6 点了,要想手工算出所有结果,看来是不可能了。你能帮帮他,让他再见到那个 m m mm mm 吗?

输入数据

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

输出数据

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

样例输入

7 2 4
6 2 4

样例输出

LOST
WIN


前面看到的关于巴什博弈的题大多数都是取到或达到最后值的人获胜,本题却是最后取硬币的人输,但是从本质上来看都是一样的。

首先要知道,如果 n % ( p + q ) = = 0 n\%(p+q)==0 n%(p+q)==0,那么先手必胜。策略是先手第一次取 q q q 个,之后每一轮假设后手取 i i i 个,那么先手就取 p + q − i p+q-i p+qi 个。这样一来每轮都在硬币堆中取了 p + q p+q p+q 个,那么最后一轮必然是后手取 p p p 个;

如果 n % ( p + q ) = k < = p n\%(p+q)=k <= p n%(p+q)=k<=p,那么无论先手第一次取多少(设为 i i i),后手都取 p + q − i p+q-i p+qi,则最后必然是先手取 k k k个;

如果 n % ( p + q ) = k > p n\%(p+q)=k > p n%(p+q)=k>p,那么先手第一次取 j j j 个,使得 ( k − j ) % ( p + q ) < p (k-j)\%(p+q)<p (kj)%(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;
}

END


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theSerein

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值