博弈论试题集.docx
《博弈论试题集.docx》由会员分享,可在线阅读,更多相关《博弈论试题集.docx(52页珍藏版)》请在冰豆网上搜索。
博弈论试题集
(一)巴什博奕(BashGame):
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。
最后取光者得胜。
显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。
因此我们发现了如何取胜的法则:
如果n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。
总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
这个游戏还可以有一种变相的玩法:
两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜。
取石子
(一)
时间限制:
3000 ms | 内存限制:
65535 KB
难度:
2
描述
一天,TT在寝室闲着无聊,和同寝的人玩起了取石子游戏,而由于条件有限,他/她们是用旺仔小馒头当作石子。
游戏的规则是这样的。
设有一堆石子,数量为N(1<=N<=1000000),两个人轮番取出其中的若干个,每次最多取M个(1<=M<=1000000),最先把石子取完者胜利。
我们知道,TT和他/她的室友都十分的聪明,那么如果是TT先取,他/她会取得游戏的胜利么?
输入
第一行是一个正整数n表示有n组测试数据
输入有不到1000组数据,每组数据一行,有两个数N和M,之间用空格分隔。
输出
对于每组数据,输出一行。
如果先取的TT可以赢得游戏,则输出“Win”,否则输出“Lose”(引号不用输出)
样例输入
2
10001
1100
样例输出
Lose
Win
最优解:
#include
usingnamespacestd;
intmain()
{
intk;
longm,n;
cin>>k;
while(k--)
{
cin>>n>>m;
if(n%(m+1)==0)
cout<<"Lose"<else
cout<<"Win"<}
}
巴什博弈变形:
有两种解,依实际情况而定:
取石子(七)
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
1
描述
Yougth和Hrdv玩一个游戏,拿出n个石子摆成一圈,Yougth和Hrdv分别从其中取石子,谁先取完者胜,每次可以从中取一个或者相邻两个,Hrdv先取,输出胜利着的名字。
输入
输入包括多组测试数据。
每组测试数据一个n,数据保证int范围内。
输出
输出胜利者的名字。
样例输入
2
3
样例输出
Hrdv
Yougth
解一:
#include
intn;
intmain()
{
while(~scanf("%d",&n))
printf(n>=3?
"Yougth\n":
"Hrdv\n");
return0;
}
解二:
3的倍数的是Yougth嬴
#include
usingnamespacestd;
intmain()
{
inta;
while(cin>>a)
{
if(a%3!
=0)
cout<<"Hrdv"<elsecout<<"Yougth"<}
return0;
}
尼姆博弈基本思想:
两人从n堆物品中取任意个,先取完者胜。
即将n堆物品的数量异或,得到的值如果为0,则先手败,反之先手胜。
如果要求先手在胜的条件下,到奇异局势的方法数,则判断异或的值与每一堆原值异或后(结果应该表示该堆没有参加异或时的异或值)与原值比较大小,
如果小于,则方法数加一。
且对应的方法后,该堆的数目应变为异或的值与每一堆原值异或的值。
取石子
(二)
时间限制:
3000 ms | 内存限制:
65535 KB
难度:
5
描述
小王喜欢与同事玩一些小游戏,今天他们选择了玩取石子。
游戏规则如下:
共有N堆石子,已知每堆中石子的数量,并且规定好每堆石子最多可以取的石子数(最少取1颗)。
两个人轮流取子,每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),并且取的石子数量不能多于该堆石子规定好的最多取子数,等哪个人无法取子时就表示此人输掉了游戏。
假设每次都是小王先取石子,并且游戏双方都绝对聪明,现在给你石子的堆数、每堆石子的数量和每堆石子规定的单次取子上限,请判断出小王能否获胜。
输入
第一行是一个整数T表示测试数据的组数(T<100)
每组测试数据的第一行是一个整数N(1(0<=m,n<=2^31)
输出
对于每组测试数据,输出Win表示小王可以获胜,输出Lose表示小王必然会败。
样例输入
2
1
10001
2
11
11
样例输出
Lose
Lose
提示
注意下面一组测试数据
2
11
22
正确的结果应该是Win
因为小王会先从第二堆石子中取一个石子,使状态变为
11
12
这种状态下,无论对方怎么取,小王都能获胜。
最优解
#include
intmain(){
intT;
scanf("%d",&T);
while(T--){
intm,n,g,sum=0;
scanf("%d",&g);
while(g--){scanf("%d%d",&m,&n);sum^=m%(n+1);}
puts(sum?
"Win":
"Lose");
}
}
一般解:
#include
usingnamespacestd;
#include
boolHandleEachCase();
intmain(){
intiCaseCount;
cin>>iCaseCount;
while(iCaseCount--){
if(HandleEachCase()){
cout<<"Win"<}else{
cout<<"Lose"<}
}
}
boolHandleEachCase(){
longlongmagic=0;
longlongiCount;
longlongm,n;
cin>>iCount;
for(inti=0;icin>>m>>n;
m%=(n+1);
magic^=m;
}
returnmagic!
=0;
}
取石子(六)
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
3
描述
最近TopCoder的PIAOYI和HRDV很无聊,于是就想了一个游戏,游戏是这样的:
有n堆石子,两个人轮流从其中某一堆中任意取走一定的石子,最后不能取的为输家,注意:
每次只能从一堆取任意个,可以取完这堆,但不能不取。
假设PIAOYI先取石子,请你帮他判断他是否能赢(假设他们取的过程中不发生失误,他们足够聪明
)。
输入
第一行输入n,代表有n组测试数据(n<=10000)
以下每组测试数据包含两行:
第一行:
包含一个整数m,代表本组测试数据有m(m<=1000)堆石子;
:
第二行:
包含m个整数Ai(Ai<=100),分别代表第i堆石子的数量。
输出
若PIAOYI赢输出“PIAOYI”,否则输出“HRDV”注意每组结果占一行。
。
样例输入
3
2
11
3
3811
2
510
最优解:
#include
#include
usingnamespacestd;
voidin(int&a)
{
charch;
while((ch=getchar())<'0'||ch>'9');
for(a=0;ch>='0'&&ch<='9';ch=getchar())a=a*10+ch-'0';
}
intmain()
{
intT;in(T);
while(T--)
{
intn;in(n);
intans=0;
for(inti=0;i!
=n;++i)
{
intb;in(b);
ans^=b;
}
if(ans)puts("PIAOYI");
elseputs("HRDV");
}return0;
}
取石子(三)
时间限制:
1000 ms | 内存限制:
1000 KB
难度:
6
描述
小王喜欢与同事玩一些小游戏,今天他们选择了玩取石子。
游戏规则如下:
共有N堆石子,已知每堆中石子的数量,两个人轮流取子,每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),取过子之后,还可以将该堆石子中剩下的任意多个石子中随意选取几个放到其它的任意一堆或几堆上。
等哪个人无法取子时就表示此人输掉了游戏。
注意,一堆石子没有子之后,就不能再往此处放石子了。
假设每次都是小王先取石子,并且游戏双方都绝对聪明,现在给你石子的堆数、每堆石子的数量,请判断出小王能否获胜。
例如:
如果最开始有4堆石子,石子个数分别为3142,而小王想决定要先拿走第三堆石子中的两个石子(石子堆状态变为3122),然后他可以使石子堆达到的状态有以下几种:
3122(不再移动石子)
4112(移动到第一堆一个)
3212(移动到第二堆一个)
3113(移动到第四堆一个)
5102(全部移动到第一堆)
3302(全部移动到第二堆)
3104(全部移动到最后)
输入
可能有多组测试数据(测试数据组数不超过1000)
每组测试数据的第一行是一个整数,表示N(1<=N<=10)
第二行是N个整数分别表示该堆石子中石子的数量。
(每堆石子数目不超过100)
当输入的N为0时,表示输入结束
输出
对于每组测试数据,输出Win表示小王可以获胜,输出Lose表示小王必然会败。
样例输入
3
213
2
11
0
样例输出
Win
Lose
一般解:
#include
#include
#include
usingnamespacestd;
boolHandleEachCase();
intmain(){
while(HandleEachCase()){
//emptywhile
}
}
boolHandleEachCase(){
intiCount;
intcount[101];
memset(count,0,sizeof(count));
cin>>iCount;
if(!
iCount){
returnfalse;
}
intiStone;
for(inti=0;icin>>iStone;
++count[iStone];
}
intmagic=0;
for(inti=0;i<101&&!
magic;++i){
magic+=count[i]&1;
}
if(magic){
cout<<"Win"<}else{
cout<<"Lose"<}
returntrue;
}
分析:
显然,如果石头是能够两两配对,每一对的数目相同,比如:
2,3,2,4可以配对成(2,2),(4,4),这样的话就是先拿的输,因为后拿的可以使自己拿完后仍然能够使得两两配对,且每一对的数目相同.
刚刚开始的时候,如果已经两两配对了,那么先拿的输了,否则,选拿的可以把最大的动一下手脚,使剩下的两两配对,且每一对的数目相同.
最优解:
#include
#include
usingnamespacestd;
boolok(intstone[])
{
for(inti=0;i!
=110;i++)
if(stone[i]&1)returntrue;
returnfalse;
}
intmain()
{
intstone[110];
intn,m;
while(cin>>n&&n)
{
memset(stone,0,sizeof(stone));
while(n--)
{
cin>>m;
stone[m]++;
}
cout<<(ok(stone)?
"Win":
"Lose")<}
}
威佐夫博奕(WythoffGame):
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
这种情况下是颇为复杂的。
我们用(ak,bk)(ak≤bk,k=0,1,2,...,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。
前几个奇异局势是:
(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而bk=ak+k,奇异局势有如下三条性质:
1。
任何自然数都包含在一个且仅有一个奇异局势中。
由于ak是未在前面出现过的最小自然数,所以有ak>ak-1,而bk=ak+k>ak-1+k-1=bk-1>ak-1。
所以性质1。
成立。
2。
任意操作都可将奇异局势变为非奇异局势。
事实上,若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。
如果使(ak,bk)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。
3。
采用适当的方法,可以将非奇异局势变为奇异局势。
假设面对的局势是(a,b),若b=a,则同时从两堆中取走a个物体,就变为了奇异局势(0,0);如果a=ak,b>bk,那么,取走b -bk个物体,即变为奇异局势;如果a=ak, bak,b=ak+k,则从第一堆中拿走多余的数量a-ak即可;如果a 从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?
我们有如下公式:
ak=[k(1+√5)/2],bk=ak+k (k=0,1,2,...,n方括号表示取整函数)奇妙的是其中出现了黄金分割数(1+√5)/2=1。
618...,因此,由ak,bk组成的矩形近似为黄金矩形,由于2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],若a=[j(1+√5)/2],那么a=aj,bj=aj+j,若不等于,那么a=aj+1,bj+1=aj+1+j+1,若都不是,那么就不是奇异局势。
然后再按照上述法则进行,一定会遇到奇异
局势。
取石子(四)
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
4
描述
有两堆石子,数量任意,可以不同。
游戏开始由两个人轮流取石子。
游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。
最后把石子全部取完者为胜者。
现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
输入
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。
输出
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
样例输入
21
84
47
样例输出
0
1
0
最优解:
#include
#include
usingnamespacestd;
intmain()
{
intm,n;
while(cin>>m>>n)
{
if(m>n)
{
inttemp;
temp=m;
m=n;
n=temp;
}
intk=n-m;
intdata=floor(k*(1.0+sqrt(5.0))/2.0);
if(data==m)
cout<<0<else
cout<<1<}
}
WythoffGame
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
1
描述
最近ZKC同学在学博弈,学到了一个伟大的博弈问题--威佐夫博弈。
相信大家都学过了吧?
没学过?
没问题。
我将要为你讲述一下这个伟大的博弈问题。
有两堆石子,数量任意,可以不同。
游戏开始由两个人轮流取石子。
游戏规定,每次有两种不同的取法:
一是可以在任意的一堆中取走任意多的石子;
二是可以在两堆中同时取走相同数量的石子。
最后把石子全部取完者为胜者。
我们今天要做的是求前n个必败态。
什么是必败态?
比如我们把(a,b)称为一种状态,a,b分别为两堆石子中所剩的数目。
如果a=0,b=0,我们说该种状态为必败态,因为我不能再进行游戏,即使是可以进行,那也是必败的,你知道,游戏的我们都是非常聪明的。
(0,0)(1,2)(3,5)...都是必败态,我们今天要做的就是求前n个必败态。
不会?
好吧!
我再告诉你:
假设第n个必败态为(a,b)a为前n-1个必败态中没有出现的最小自然数,b=a+n。
这下大家应该明白了吧。
好吧,我们的任务就的要前n个必败态。
规定第0个必败态为(0,0)。
输入
多组数据。
输入为一个数n(0<=n<=100000)。
输出
按照要求求出前n个必败态。
输出格式看下面样例。
样例输入
3
1
样例输出
(0,0)(1,2)(3,5)(4,7)
(0,0)(1,2)
提示
注意:
每种情况中间没有空格
#include
#include
typedefstructNode
{
inta,b;
}N;
Nres[100001];
voidinit()
{
res[0].a=0;
res[0].b=0;
for(inti=1;i<100001;i++)
{
res[i].a=(1+sqrt(5))*i/2;
res[i].b=res[i].a+i;
}
}
intmain()
{
intn;
init();
while(scanf("%d",&n)!
=EOF)
{
for(inti=0;i<=n;i++)
{
printf("(%d,%d)",res[i].a,res[i].b);
}
printf("\n");
}
return0;
}
本人自己写的代码:
#include
intmain()
{
intn,k,b,a[100001],i,m=0;
a[0]=0;
while(~scanf("%d",&n))
{
if(n<=m)
{
for(k=0;k<=n;k++)
{
printf("(%d,%d)",a[k],a[k]+k);
}
}else
{
printf("(%d,%d)",a[0],a[0]);
for(k=1;k<=n;k++)
{
b=a[k-1]+1;
for(i=k-1;i>=0;i--)
{
if(b==(a[i]+i))
{
b++;
}elseif(b>(a[i]+i))
{
break;
}
}
a[k]=b;
printf("(%d,%d)",a[k],a[k]+k);
}
m=n;
}
printf("\n");
}
return0;
}
取石子(八)
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
3
描述
有两堆石子,数量任意,可以不同。
游戏开始由两个人轮流取石子。
游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。
最后把石子全部取完者为胜者。
现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
如果你胜,你第1次怎样取子?
输入
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000。
a=b=0退出。
输出
输出也有若干行,如果最后你是败者,则为0,反之,输出1,并输出使你胜的你第1次取石子后剩下的两堆石子的数量x,y,x<=y。
如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况,假如取一堆的有多种情况,先输出从石子多的一堆中取的情况,且要求输出结果保证第二个值不小于第一个值。
样例输入
12
57
22
00
样例输出
0
1
35
35
47
1
00
12
最优解:
#include
#include
#include
#include
usingnamespacestd;
intmain()
{
inta,b,temp,temp2,k,i;
while(scanf("%d%d",&a,&b),a+b)
{
if(a>b)
s);
k=b-a;
temp=k*(1.0+sqrt(5.0))/2.0;
if(a==temp)//奇异局势
printf("0\n");
else