(一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规
定每次至少取一个,最多取m个。最后取光者得胜。
Link:http://acm.hdu.edu.cn/showproblem.php?pid=1846
Brave Game
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 7040 Accepted Submission(s): 4717
今天,大家选择上机考试,就是一种勇敢(brave)的选择;这个短学期,我们讲的是博弈(game)专题;所以,大家现在玩的也是“勇敢者的游戏”,这也是我命名这个题目的原因。
当然,除了“勇敢”,我还希望看到“诚信”,无论考试成绩如何,希望看到的都是一个真实的结果,我也相信大家一定能做到的~
各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;
如果游戏的双方使用的都是最优策略,请输出哪个人能赢。
每组测试数据占一行,包含两个整数n和m(1<=n,m<=1000),n和m的含义见题目描述。
2 23 2 4 3
first second
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int c,n,m,sum;
scanf("%d",&c);
while(c--)
{
scanf("%d%d",&n,&m);
sum=n%(m+1);
if(sum==0)
printf("second\n");
else
printf("first\n");
}
return 0;
}
kiki's game
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 40000/1000 K (Java/Others)Total Submission(s): 7012 Accepted Submission(s): 4186
5 3 5 4 6 6 0 0
What a pity! Wonderful! Wonderful!
- 博弈规则:
- 一个状态是必败,当且仅当所有后继都是必胜。
- 一个状态是必胜,当且仅当至少有一个后继是必败。
思路:从(1,m)出发,最终到达(n,1),我们可以倒着推,(n,1)是必败态,(n,2),(n-1,1)以及(n-1,2)都是必胜态,这样从左下角往右上角推(只能到达必胜态的状态是必败态,可以到达必败态的状态是必胜态),发现每四个方格都是一样的只有一个必败态,简单的画画就知道了。
然后得出的结论是当n和m为奇数时,右上角为必败态,即当m、n均为奇数时,先移动的只能到达必胜点(即下一个选手取胜),开始处于必败态的选手自然是输。
规律如图可得:
1 2 3 4 5 . . .
1 P N P N P
2 N N N N N
3 P N P N P
4 N N N N N
5 P N P N P
.
.
.
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int n,m,ans;
while(scanf("%d%d",&n,&m)==2)
{
if(n==0&&m==0)
break;
if((n&1)&&(m&1))
printf("What a pity!\n");
else
printf("Wonderful!\n");
}
return 0;
}
Link: http://acm.hdu.edu.cn/showproblem.php?pid=2149
Public Sale
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4574 Accepted Submission(s): 2768
要种田得有田才行,Lele听说街上正在举行一场别开生面的拍卖会,拍卖的物品正好就是一块20亩的田地。于是,Lele带上他的全部积蓄,冲往拍卖会。
后来发现,整个拍卖会只有Lele和他的死对头Yueyue。
通过打听,Lele知道这场拍卖的规则是这样的:刚开始底价为0,两个人轮流开始加价,不过每次加价的幅度要在1~N之间,当价格大于或等于田地的成本价 M 时,主办方就把这块田地卖给这次叫价的人。
Lele和Yueyue虽然考试不行,但是对拍卖却十分精通,而且他们两个人都十分想得到这块田地。所以他们每次都是选对自己最有利的方式进行加价。
由于Lele字典序比Yueyue靠前,所以每次都是由Lele先开始加价,请问,第一次加价的时候,
Lele要出多少才能保证自己买得到这块地呢?
每组测试包含两个整数M和N(含义见题目描述,0<N,M<1100)
如果Lele在第一次无论如何出价都无法买到这块土地,就输出"none"。
4 2 3 2 3 5
1 none 3 4 5
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int n,m,ans,i,cnt;
while(scanf("%d%d",&m,&n)!=EOF)
{
if(n>=m)//n<=m先手必胜
{
for(i=m;i<n;i++)
printf("%d ",i);
printf("%d\n",n);
}
else
{
if(m%(n+1)==0)//必败态
{
printf("none\n");
}
else
{
i=1;
cnt=0;
while(i<=n)
{
if((m-i)%(n+1)==0)//必须留下m+1的倍数
{
cnt++;
if(cnt==1)
{
printf("%d",i);
}
else
{
printf(" %d",i);
}
}
i++;
}
puts("");
}
}
}
return 0;
}
悼念512汶川大地震遇难同胞——选拔志愿者
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5829 Accepted Submission(s): 3654
选拔规则如下:
1、最初的捐款箱是空的;
2、两人轮流捐款,每次捐款额必须为正整数,并且每人每次捐款最多不超过m元(1<=m<=10)。
3、最先使得总捐款额达到或者超过n元(0<n<10000)的一方为胜者,则其可以亲赴灾区服务。
我们知道,两人都很想入选志愿者名单,并且都是非常聪明的人,假设林队先捐,请你判断谁能入选最后的名单?
2 8 10 11 10
Grass Rabbit
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int T,n,m,ans,i,cnt;
while(scanf("%d",&T)!=EOF)
{
while(T--)
{
scanf("%d%d",&n,&m);
if(n%(m+1)==0)
printf("Rabbit\n");
else
printf("Grass\n");
}
}
return 0;
}
理论:(来自:http://www.wutianqi.com/?p=1081)
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
Link:http://acm.hdu.edu.cn/showproblem.php?pid=1527
取石子游戏
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 4644 Accepted Submission(s): 2457
2 1 8 4 4 7
0 1 0
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
int main()
{
int n, m;
int k;
double x=(1+sqrt(5.0))/2;
while(scanf("%d%d",&n,&m)!=EOF && n+m)
{
if(n>m) //如果 n > m,根据奇异局势应该要a<b
{
n^=m; //通过三个异或 交换n、m
m^=n;
n^=m;
}
k=m-n; //显然,遇到奇异局势的人一定会输。
if((int)(k*x)==n)
printf("0\n"); //因为奇异局势变成非奇异局势后,下一个人一定可以将局势
//再次变成奇异局势,所以一定还是第一次遇到奇异局势的人遇到奇异局势,则他定输
else
{
printf("1\n");//碰到非奇异局势,将其变成奇异局势,可能有两种变法。
/*for(int i=1;i<=n;i++)//①:同时从两堆中拿相同数目的石子。
{
int a=n-i , b=m-i;
k=b-a; //cout<<"a= "<<a<<" b= "<<b<<" "<<x<<endl;
if((int)(k*x)==a) printf("%d %d\n",a, b);
}//cout<<"111111111111"<<endl;
for(int i=m-1;i>=0;i--)//②:从一堆中取。
{
int a=n , b=i;
if(a > b) //同理,保持奇异局势中 a < b
{
a^=b;
b^=a;
a^=b;
}
k=b-a;
if((int)(k*x)==a) printf("%d %d\n",a , b);
}//cout<<"22222222222222"<<endl;*/
}
}
return 0;
}
Link:http://acm.hdu.edu.cn/showproblem.php?pid=2177
取(2堆)石子游戏
Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1584 Accepted Submission(s): 959
1 2 5 8 4 7 2 2 0 0
0 1 4 7 3 5 0 1 0 0 1 2
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
int main()
{
int n, m;
int k;
double x=(1+sqrt(5.0))/2;
while(scanf("%d%d",&n,&m)!=EOF && n+m)
{
if(n>m) //如果 n > m,根据奇异局势应该要a<b
{
n^=m; //通过三个异或 交换n、m
m^=n;
n^=m;
}
k=m-n; //显然,遇到奇异局势的人一定会输。
if((int)(k*x)==n)
printf("0\n"); //因为奇异局势变成非奇异局势后,下一个人一定可以将局势
//再次变成奇异局势,所以一定还是第一次遇到奇异局势的人遇到奇异局势,则他定输
else
{
printf("1\n");//碰到非奇异局势,将其变成奇异局势,可能有两种变法。
for(int i=1;i<=n;i++)//①:同时从两堆中拿相同数目的石子。
{
int a=n-i , b=m-i;
k=b-a; //cout<<"a= "<<a<<" b= "<<b<<" "<<x<<endl;
if((int)(k*x)==a) printf("%d %d\n",a, b);
}//cout<<"111111111111"<<endl;
for(int i=m-1;i>=0;i--)//②:从一堆中取。
{
int a=n , b=i;
if(a > b) //同理,保持奇异局势中 a < b
{
a^=b;
b^=a;
a^=b;
}
k=b-a;
if((int)(k*x)==a) printf("%d %d\n",a , b);
}//cout<<"22222222222222"<<endl;
}
}
return 0;
}
(三)尼姆博奕(Nimm Game): 有三堆各有若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
用(a , b , c)表示某种局势,首先(0, 0 , 0)显然是奇异局势(即后取得人一定取光物品),第二种奇异局势是(0 , n , n),只要与对手拿走同样多的物品,最后都将导致(0 , 0 , 0)。第三种(1 , 2 , 3)也是奇异局势,无论对手如何拿,接下来都可以变成(0 , n , n)的情况。
用(+)表示异或(也叫按位模2加)运算,对于任何奇异局势,a1 (+) a2 (+) ……(+) an = 0。
取火柴的游戏
题目1:今有若干堆火柴,两人依次从中拿取,规定每次只能从一堆中取若干根, 可将一堆全取走,但不可不取,最后取完者为胜,求必胜的方法。
题目2:今有若干堆火柴,两人依次从中拿取,规定每次只能从一堆中取若干根, 可将一堆全取走,但不可不取,最后取完者为负,求必胜的方法。
定义:若所有火柴数异或为0,则该状态被称为利他态,用字母T表示;否则, 为利己态,用S表示。
[定理1]:对于任何一个S态,总能从一堆火柴中取出若干个使之成为T态。
[定理2]:T态,取任何一堆的若干根,都将成为S态。
[定理 3]:S态,只要方法正确,必赢。
[定理4]:T态,只要对方法正确,必败。
接着来解决第二个问题。
定义:若一堆中仅有1根火柴,则被称为孤单堆。若大于1根,则称为充裕堆。
定义:T态中,若充裕堆的堆数大于等于2(若充裕堆数为1,则最后异或一定不为0 ,因为高位只有一个1,异或结果一定不为0),则称为完全利他态,用T2表示;若充裕堆的堆数等于0,则称为部分利他态,用T0(说明孤单堆的数目一定是偶数,否则异或结果定不为0,且因为是偶数,后拿的一定会最后取光所有物品,在第二个问题中先拿的是赢家)表示。
孤单堆的根数异或只会影响二进制的最后一位,但充裕堆会影响高位(非最后一位)。一个充裕堆,高位必有一位不为0,则所有根数异或不为0。故不会是T态。
[定理5]:S0态,即仅有奇数个孤单堆,必败。T0态必胜。
[定理6]:S1态( 一堆充裕堆,x个孤单堆),只要方法正确,必胜。
证明:
若此时孤单堆堆数为奇数,把充裕堆取完 , 变成S0;
否则有偶数个孤单堆,将充裕堆取得剩一根。这样,就变成奇数个孤单堆即 S0,由对方取。
由定理5 ,必胜。
[定理7]:S2态不可转一次变为T0态。
[定理8]:S2态可一次转变为T2态。
(若充裕堆>2 (1:充裕堆为偶数,孤单堆为奇数,将一个孤单堆取完),(2:充裕堆为奇数,若孤单堆为偶数,则取完一个充裕堆,若孤单堆为奇数,则取得一个充裕堆剩1个。))
[定理9]:T2态,只能转变为S2态或S1态。( 若充裕堆>2则可转变为S2 ,若充裕堆=2,则可转变为S1)。
[定理10]:S2态,只要方法正确,必胜.
证明:
方法如下:
1) S2态(①),就把它变为T2态(②)。(由定理8)
2) 对方只能T2转变成S2态或S1态(①)(定理9)
若转变为S2, 转向1)
若转变为S1(①), 这己必胜。(定理5)
定理11]:T2态必输。
综上所述,若是 S2,S1,T0 。 则先下的人必胜。
若是 T2,S0 。则先下的人必输。
两题比较:
第一题(全过程): S2 ->T2 ->S2->T2->... ...->T2->S1->T0(偶数个孤单堆)->...->S0->T0 (全0)。
第二题(全过程):S2->T2->S2->T2-> …… ->T2->S1->S0(技术个孤单堆)->T0->S0->……->S0->T0(全0)
S1态可以转变为S0态(第二题做法),也可以转变为
T0(第一题做法)。哪一方控制了S1态,他即可以有办法使自己得到最后一根(转变为
T0),也可以使对方得到最后一根(转变为S0)。
所以,抢夺S1是制胜的关键!
为此,始终把T2态让给对方,将使对方处于被动状态,他早晚将把状态变为S1.
Link:http://acm.hdu.edu.cn/showproblem.php?pid=1907
John
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)Total Submission(s): 3740 Accepted Submission(s): 2112
Both of players are using optimal game strategy. John starts first always. You will be given information about M&Ms and your task is to determine a winner of such a beautiful game.
Constraints:
1 <= T <= 474,
1 <= N <= 47,
1 <= Ai <= 4747
2 3 3 5 1 1 1
John Brother
AC code:
#include <iostream>
#include <algorithm>
using namespace std;
int arr[48];
int main()
{
int t,n,i,temp;
cin>>t;
while( t-- )
{
cin>>n;
for(i=0;i<n;++i)
cin>>arr[i];
sort(arr,arr+n);
// 如果全是1,按照奇偶判断谁获胜
if( arr[n-1]==1 )
{
if( n&1 ) cout<<"Brother"<<endl;
else cout<<"John"<<endl;
continue;
}
// 异或加起来
temp=arr[0]^arr[1];
for(i=2;i<n;++i)
temp^=arr[i];
if( temp==0 ) cout<<"Brother"<<endl;
else cout<<"John"<<endl;
}
return 0;
}