我们不难发现,面临m+1个石子的人一定失败。
这样的话两个人的最优策略一定是通过拿走石子,使得对方拿石子时还有m+1个
我们再向一般情况推广
当n=(m+1)\(\times\)k时,先手必负,因为对于每m+1个石子,先手取走k个后,后手总能取走v个,使得k+v=m+1,这样,先手最终会面临m+1的必负情况
当n\(\neq\)(m+1)\(\times\)k时,先手在第一次可以拿走一定的石子,使得n可以被(m+1)整除,这样就转变成了上面的情况
所以结论为:当n能整除m+1时先手必败,否则先手必胜。
例题
1、HDU 1864 Brave Game
题意
十年前读大学的时候,中国每年都要从国外引进一些电影大片,其中有一部电影就叫《勇敢者的游戏》(英文名称:Zathura),一直到现在,我依然对于电影中的部分电脑特技印象深刻。
今天,大家选择上机考试,就是一种勇敢(brave)的选择;这个短学期,我们讲的是博弈(game)专题;所以,大家现在玩的也是“勇敢者的游戏”,这也是我命名这个题目的原因。
当然,除了“勇敢”,我还希望看到“诚信”,无论考试成绩如何,希望看到的都是一个真实的结果,我也相信大家一定能做到的~
各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;
如果游戏的双方使用的都是最优策略,请输出哪个人能赢。
输入格式
输入数据首先包含一个正整数C(C<=100),表示有C组测试数据。
每组测试数据占一行,包含两个整数n和m(1<=n,m<=1000),n和m的含义见题目描述。
输出格式
如果先走的人能赢,请输出“first”,否则请输出“second”,每个实例的输出占一行。
样例
样例输入
2
23 2
4 3
样例输出
first
second
分析
裸的板子,没什么好说的
代码
#include
using namespace std;
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
int ans=n%(m+1);
if(ans==0) printf("second\n");
else printf("first\n");
}
return 0;
}
2、UDU 2188 悼念512汶川大地震遇难同胞——选拔志愿者
题意
对于四川同胞遭受的灾难,全国人民纷纷伸出援助之手,几乎每个省市都派出了大量的救援人员,这其中包括抢险救灾的武警部队,治疗和防疫的医护人员,以及进行心理疏导的心理学专家。根据要求,我校也有一个奔赴灾区救灾的名额,由于广大师生报名踊跃,学校不得不进行选拔来决定最后的人选。经过多轮的考核,形势逐渐明朗,最后的名额将在“林队”和“徐队”之间产生。但是很巧合,2个人的简历几乎一模一样,这让主持选拔的8600很是为难。无奈,他决定通过捐款来决定两人谁能入选。
选拔规则如下:
1、最初的捐款箱是空的;
2、两人轮流捐款,每次捐款额必须为正整数,并且每人每次捐款最多不超过m元(1<=m<=10)。
3、最先使得总捐款额达到或者超过n元(0 我们知道,两人都很想入选志愿者名单,并且都是非常聪明的人,假设林队先捐,请你判断谁能入选最后的名单?
输入格式
输入数据首先包含一个正整数C,表示包含C组测试用例,然后是C行数据,每行包含两个正整数n,m,n和m的含义参见上面提到的规则。
输出格式
对于每组测试数据,如果林队能入选,请输出字符串"Grass", 如果徐队能入选,请输出字符串"Rabbit",每个实例的输出占一行。
样例
样例输入
2
8 10
11 10
样例输出
Grass
Rabbit
分析
上一道题的代码改改输出就能过了
(这两道板子题主要是为了找自信)
代码
#include
using namespace std;
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
int ans=n%(m+1);
if(ans==0) printf("Rabbit\n");
else printf("Grass\n");
}
return 0;
}
3、Ticket Game CodeForces - 1215D 博弈题
题目描述
Monocarp and Bicarp live in Berland, where every bus ticket consists of n digits (n is an even number). During the evening walk Monocarp and Bicarp found a ticket where some of the digits have been erased. The number of digits that have been erased is even.
Monocarp and Bicarp have decided to play a game with this ticket. Monocarp hates happy tickets, while Bicarp collects them. A ticket is considered happy if the sum of the first \(\frac{n}{2}\) digits of this ticket is equal to the sum of the last \(\frac{n}{2}\) digits.
Monocarp and Bicarp take turns (and Monocarp performs the first of them). During each turn, the current player must replace any erased digit with any digit from 0 to 9. The game ends when there are no erased digits in the ticket.
If the ticket is happy after all erased digits are replaced with decimal digits, then Bicarp wins. Otherwise, Monocarp wins. You have to determine who will win if both players play optimally.
输入格式
The first line contains one even integer n (2≤n≤2⋅105) — the number of digits in the ticket.
The second line contains a string of n digits and "?" characters — the ticket which Monocarp and Bicarp have found. If the ii-th character is "?", then the ii-th digit is erased. Note that there may be leading zeroes. The number of "?" characters is even.
输出格式
If Monocarp wins, print "Monocarp" (without quotes). Otherwise print "Bicarp" (without quotes).
样例
Input
4
0523
Output
Bicarp
Input
2
??
Output
Bicarp
Input
8
?054??0?
Output
Bicarp
Input
6
???00?
Output
Monocarp
分析
一句话题意:一张票有n位数,如果这张票的前一半数字的和等于后一半数字的和(n一定是偶数),就称这张票为快乐票。有些数被擦除了,标记为’?’(’?‘的个数也是偶数),现在Monocarp 和 Bicarp 进行一个游戏,两人轮流将’?'变换成0到9的任意一个数,Monocarp先手,如果最后票为快乐票则Bicarp赢,否则Monocarp赢。
其实就是巴什博弈变了一下型,思想是一样的
我们分情况来考虑一下
1、左边的数字之和等于右边的数字之和
这时,如果左右两边?的个数相等的话,后手赢,因为先手无论放什么数,后手都可以放一个相同的数来平衡
如果不相等,则先手必胜,因为最后肯定只能在一边放,只要先手能放,就一定会打破平衡
2、左边的数字之和小于右边的数字之和
如果左边?的个数大于等于右边的个数,那么先手必胜,因为先手可以一直在左边放9,后手只能不断在右边放9维持差距,但始终不能弥补差距
如果左边?的个数小于右边的个数,我们设左边?的个数为a,右边?的个数为b,那么前2a回合,策略同上;2a回合之后,先手放一个数x,后手唯一的选择就是放一个y,使x+y=9,所以当左右两边数字之差为9的倍数时,后手胜,否则先手胜
3、右边的数字之和小于左边的数字之和(同上)
代码
#include
#include
#include
#include
#include
#include
using namespace std;
char c[300000];
int lefw,rigw,lef,rigt;
int main(){
int n;
scanf("%d",&n);
getchar();
for(int i=1;i<=n/2;i++){
scanf("%c",&c[i]);
if(c[i]=='?'){
lefw++;
} else {
lef+=(c[i]-'0');
}
}
for(int i=n/2+1;i<=n;i++){
scanf("%c",&c[i]);
if(c[i]=='?'){
rigw++;
} else {
rigt+=(c[i]-'0');
}
}
if(lef==rigt){
if(lefw==rigw) printf("Bicarp\n");
else printf("Monocarp\n");
}
else if(lef>rigt){
int cha=rigw-lefw;
if(cha<=0){
printf("Monocarp\n");
return 0;
}
int chaa=lef-rigt;
if(cha%2==0 && cha/2*9==chaa) printf("Bicarp\n");
else printf("Monocarp\n");
} else {
int cha=lefw-rigw;
if(cha<=0){
printf("Monocarp\n");
return 0;
}
int chaa=rigt-lef;
if(cha%2==0 && cha/2*9==chaa) printf("Bicarp\n");
else printf("Monocarp\n");
}
return 0;
}
C、威佐夫博弈
博弈模型
有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
分析
我们先来考虑一下什么情况下先手必负
一、石子状态为(0,0)
很显然,先手必负
二、石子状态为(1,2)
如果先手把左面那一堆石子全部拿走,那么后手可以把右面那一堆石子全部拿走,先手负
如果先手把右面那一堆石子全部拿走,那么后手可以把左面那一堆石子全部拿走,先手负
如果先手只在右面拿一个石子,那么后手可以一次性把两堆石子都取走,先手负
如果先手在左面和右面各取一个石子,那么后手可以把右面的石子全部拿走,先手负
三、石子状态为(3,5)
如果先手在左面的石子中取1个,那么后手可以在右面那一堆中取4个,这样状态就变成了(1,2),先手负
如果先手在左面的石子中取2个,那么后手可以在右面的石子中取3个,状态又变成了(1,2),先手负
如果先手把左面的石子全部取完,那么后手也可以把右面的石子全部取完,先手负
如果先手在右面的石子中取1个,那么后手可以同时在两堆石子中取2个,状态又变成了(1,2),先手负
如果先手在右面的石子中取2个,那么后手可以同时在两堆石子中取3个,先手负
如果先手在右面的石子中取3个,那么后手可以同时在两堆石子中取1个,状态又变成了(1,2),先手负
如果先手在右面的石子中取4个,那么后手可以在左面的石子中取1个,状态又变成了(1,2),先手负
……
总而言之,这种情况先手负
那我们可以发现一个规律,下面的情况先手必负
第一种(0,0)
第二种(1,2)
第三种(3,5)
第四种 (4 ,7)
第五种(6,10)
第六种 (8,13)
第七种 (9 , 15)
第八种 (11 ,18)
……
第n种(a,b)
我们可以发现左右两堆的差值是逐渐递增的
而且左边的数是之前没有出现过的最小的自然数
而且更重要的是差值$\times$1.618=左边的数
而且我们知道黄金分割数=\(\frac{\sqrt{5}-1}{2}\)=0.618
所以这个比例就是黄金分割数+1(是不是很神奇)
例题
HDU 1527 取石子游戏
题目描述
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
输入格式
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。
输出格式
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
样例
Input
2 1
8 4
4 7
Output
0
1
0
分析
模板题
代码
#include
#include
#include
#include
using namespace std;
int main(){
int a,b;
double r=(sqrt(5.0)+1)/2;
while(scanf("%d%d",&a,&b)!=EOF){
if(a>b) swap(a,b);
int c=b-a;
int now=c*r;
if(now==a){
printf("0\n");
} else {
printf("1\n");
}
}
return 0;
}
总结
其实博弈问题只要掌握了模板,有了思路,解题就比较容易了