博弈-取石子

取石子(一)

时间限制: 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
1000 1
1 100
样例输出
Lose
Win


算法:
最多取m个,则如果总数n%(m+1)为0,则无论先手取了几个t1,第二个人都取t2,使得t1+t2==m+1即可获胜
相反 如果n%(m+1)!=0 ,则先手不余数去掉,之后按上述方案,即可获胜

代码:
 
#include<iostream>
using namespace std;
int main()
{
	int k;
	long m,n;
	cin>>k;
	while(k--)
	{
		cin>>n>>m;
		if(n%(m+1)==0)
			cout<<"Lose"<<endl;
		else
			cout<<"Win"<<endl;
	}
}        





取石子(二)

时间限制: 3000 ms  |  内存限制: 65535 KB
难度: 5
描述

小王喜欢与同事玩一些小游戏,今天他们选择了玩取石子。

游戏规则如下:共有N堆石子,已知每堆中石子的数量,并且规定好每堆石子最多可以取的石子数(最少取1颗)。

两个人轮流取子,每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),并且取的石子数量不能多于该堆石子规定好的最多取子数,等哪个人无法取子时就表示此人输掉了游戏。

假设每次都是小王先取石子,并且游戏双方都绝对聪明,现在给你石子的堆数、每堆石子的数量和每堆石子规定的单次取子上限,请判断出小王能否获胜。

输入
第一行是一个整数T表示测试数据的组数(T<100)
每组测试数据的第一行是一个整数N(1<N<100),表示共有N堆石子,随后的N行每行表示一堆石子,这N行中每行有两个数整数m,n表示该堆石子共有m个石子,该堆石子每次最多取n个。(0<=m,n<=2^31)
输出
对于每组测试数据,输出Win表示小王可以获胜,输出Lose表示小王必然会败。
样例输入
2
1
1000 1
2
1 1
1 1
样例输出
Lose
Lose
提示
注意下面一组测试数据
2
1 1 
2 2
正确的结果应该是Win
因为小王会先从第二堆石子中取一个石子,使状态变为
1 1
1 2
这种状态下,无论对方怎么取,小王都能获胜。

算法:

巴什博弈和尼姆博弈的杂糅

因为尼姆博弈要求对每一堆石子可以取1-全部,而这道题限制了个数,就成为了巴什博弈。那为什么可以用尼姆博弈的思想来求解呢?

因为我们可以发现,当我们使用巴什博弈取到最后一次时,得到的n%(m+1)结果肯定<m,这样就符合了尼姆博弈的要求,进而可以用尼姆博弈的异或运算来求解。


代码:

 
#include<cstdio>
int main(){
	int T; 
	scanf("%d",&T);
	while(T--){
		int m,n,g,sum=0;
		scanf("%d",&g);
		while(g--){scanf("%d%d",&m,&n);sum ^= m % (n + 1);}
		puts(sum?"Win":"Lose");
	}
}        


规则:
有x个石子,两人轮流取,最多取y个,最少取z个,且z<=y,没得取的人输,两个人都按照最优策略进行游戏



有y个石子,两人轮流取,可以取x个,x属于数集X,没得取的人输,两个人都按照最优策略进行游戏,问哪些是必胜态。
 
 
 

#include <iostream>
#include <vector>
using namespace std;
#define MAX_N 10000
bool win[MAX_N];
int main(){
    int n,x;
    cin>>x>>n;
    vector<int> s(n,0);
    int z,y;
    cin>>z>>y;
    //for(int i=0;i<n;i++){
    //    cin>>s[i];
    //}
    win[0]=false;
    for(int j=1;j<=x;j++){
        win[j]=false;
        for(int i=z;i<=y;i++){
            win[j] |= i<=j && !win[j-i];
        }
    }

    if(win[x])cout<<"first"<<endl;
     else cout<<"second"<<endl;

return 0;}


取石子(三)

时间限制: 1000 ms  |  内存限制: 1000 KB
难度: 6
描述

小王喜欢与同事玩一些小游戏,今天他们选择了玩取石子。

游戏规则如下:共有N堆石子,已知每堆中石子的数量,两个人轮流取子,每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),取过子之后,还可以将该堆石子中剩下的任意多个石子中随意选取几个放到其它的任意一堆或几堆上。等哪个人无法取子时就表示此人输掉了游戏。注意,一堆石子没有子之后,就不能再往此处放石子了。

假设每次都是小王先取石子,并且游戏双方都绝对聪明,现在给你石子的堆数、每堆石子的数量,请判断出小王能否获胜。

例如:如果最开始有4堆石子,石子个数分别为3 1 4 2,而小王想决定要先拿走第三堆石子中的两个石子(石子堆状态变为3 1 2 2),然后他可以使石子堆达到的状态有以下几种:

3 1 2 2(不再移动石子)

4 1 1 2(移动到第一堆一个)

3 2 1 2(移动到第二堆一个)

3 1 1 3(移动到第四堆一个)

5 1 0 2(全部移动到第一堆)

3 3 0 2(全部移动到第二堆)

3 1 0 4(全部移动到最后)

输入
可能有多组测试数据(测试数据组数不超过1000)
每组测试数据的第一行是一个整数,表示N(1<=N<=10)
第二行是N个整数分别表示该堆石子中石子的数量。(每堆石子数目不超过100)
当输入的N为0时,表示输入结束
输出
对于每组测试数据,输出Win表示小王可以获胜,输出Lose表示小王必然会败。
样例输入
3
2 1 3
2
1 1
0
样例输出
Win
Lose

算法:

如果石子能够两两配对,每一对中的两堆石子的个数相同,那么就是必败态,先拿的输,先拿的将必败态转化为必胜态,后拿的可以使自己拿完后仍然能够使得两两配对,且每一对的数目相同。这样就又将必败态留给对手。最后可能会变成(1, 1)的情况,这样先拿的输,后拿的赢。所以如果n是奇数的话,就不能能出现两两配对的状态那么肯定是必胜态。

结论是:如果N是奇数先手胜,如果是偶数,那么判断石子数量出现的次数是否为偶数,都是偶数那就是后者胜,只要有一个不是偶数那么先者胜。

例如4    2 1 3 2这组数据,n是偶数,1出现一次,2出现两次,3出现一次,不是所有的数出现的次数都是偶数,所以先者胜。

     4    2 1 1 2,n是偶数,1和2都出现两次,所以先者败。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[103];
int main()
{
    int n;
    while(scanf("%d",&n),n)
    {
        int b;
        memset(a,0,sizeof(a));
        for(int i=0;i<n;i++)
        {
            scanf("%d",&b);
            a[b]++;
        }
        int f=0;
        if(n%2!=0)
        {printf("Win\n");continue;}
        for(int i=0;i<=100;i++)
        {
            if(a[i]%2!=0)
            {
                f=1;
                break;
            }
        }
        if(f) printf("Win\n");
        else printf("Lose\n");
    }

    return 0;
}


取石子 (四)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
输入
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000,000。
输出
输出对应也有若干行,每行包含一个数字1或0,如果最后你是胜者,则为1,反之,则为0。
样例输入
2 1
8 4
4 7
样例输出
0
1
0


算法:

威佐夫博奕,已知k=b-a,可求出ak,如果ak==a,则必败。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int main()
{
    int a,b;
    double w=(1.0+sqrt(5.0))/2.0;
    while(scanf("%d %d",&a,&b)!=EOF)
    {
        if(a>b) swap(a,b);
        if(a==(int)floor((b-a)*w)) printf("0\n");
        else printf("1\n");

    }
}

取石子(五)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述
himdd最近很想玩游戏,于是他找到acmj和他一起玩,游戏是这样的:有一堆石子,两个人轮流从其中取走一定的石子,取走最后所有石子的人为赢家,不过得遵循如下规则:
1.第一次取不能取完,至少取1颗.

2.从第二次开始,每个人取的石子数至少为1,至多为对手刚取的石子数的两倍。

himdd事先想知道自己会不会赢,你能帮帮他吗?(每次himdd先手)

输入
有多组测试数据,每组有一个整数n(2<=n<2^64);
输出
himdd会赢输出Yes,否则输出No;
样例输入
2
5
6
样例输出
No
No
Yes

算法:

斐波那契博弈问题:

当n为Fibonacci数的时候,必败。

代码:

#include<stdio.h>
#include<stdlib.h>
long long a[100];

int main()
{
    int i,j,ok;
    long long m;
    a[1]=a[2]=1;
    for(i=3;i<=100;i++)
     a[i]=a[i-1]+a[i-2];

    while(scanf("%lld",&m)!=EOF)
    {
      ok=1;
       for(i=2;i<=92&&m<=a[92];i++)
             if(a[i]==m)
             {
                ok=0;
                printf("No\n");
                break;
             }
             if(ok) printf("Yes\n");
     }
     return 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
1 1
3
3 8 11
2
5 10
样例输出
HRDV
HRDV
PIAOYI


 
#include<iostream>
#include<stdio.h>
using namespace std;
void in(int &a)
{
	char ch;
	while((ch=getchar())<'0'||ch>'9');
	for(a=0;ch>='0'&&ch<='9';ch=getchar()) a=a*10+ch-'0';
}
int main()
{
	int T;in(T);
	while(T--)
	{
		int n;in(n);
		int ans=0;
		for(int i=0;i!=n;++i)
		{
			int b;in(b);
			ans^=b;
		}
		if(ans) puts("PIAOYI");
		else     puts("HRDV");
	}return 0;
}        


取石子(七)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 1
描述

Yougth和Hrdv玩一个游戏,拿出n个石子摆成一圈,Yougth和Hrdv分别从其中取石子,谁先取完者胜,每次可以从中取一个或者相邻两个,Hrdv先取,输出胜利着的名字。

输入
输入包括多组测试数据。
每组测试数据一个n,数据保证int范围内。
输出
输出胜利者的名字。
样例输入
2
3
样例输出
Hrdv
Yougth



 
#include<cstdio>
int n;
int main()
{
    while(~scanf("%d",&n))
        printf(n>=3?"Yougth\n":"Hrdv\n");
    return 0;
}
        


取石子(八)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 3
描述

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。如果你胜,你第1次怎样取子? 

输入
输入包含若干行,表示若干种石子的初始情况,其中每一行包含两个非负整数a和b,表示两堆石子的数目,a和b都不大于1,000,000。a=b=0退出。
输出
输出也有若干行,如果最后你是败者,则为0,反之,输出1,并输出使你胜的你第1次取石子后剩下的两堆石子的数量x,y,x<=y。如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况,假如取一堆的有多种情况,先输出从石子多的一堆中取的情况,且要求输出结果保证第二个值不小于第一个值。
样例输入
1 2 5 72 20 0
样例输出
013 53 54 710 01 2

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
bool judge(int a,int b){
    if(a>b) swap(a,b);
    double w=(1.0+sqrt(5.0))/2.0;
    if(a==(int)floor((b-a)*w))
        return true;
    return false;
}

void fun(int a,int b){
    for(int i=1;i<=b;i++){
        if(judge(a-i,b-i)){
            cout<<a-i<<" "<<b-i<<endl;
            break;
        }
    }
    for(int i=1;i<=b;i++){
        if(judge(a,b-i)){
                if(a<b-i)
                    cout<<a<<" "<<b-i<<endl;
                else
                    cout<<b-i<<" "<<a<<endl;
            break;
        }
    }
if(a!=b){
    for(int i=1;i<=a;i++){
        if(judge(a-i,b)){
            if(a-i<b)
                    cout<<a-i<<" "<<b<<endl;
                else
                    cout<<b<<" "<<a-i<<endl;
            break;
        }
    }

}
}


int main()
{
    int a,b;
    double w=(1.0+sqrt(5.0))/2.0;
    while(scanf("%d %d",&a,&b)!=EOF , a && b)
    {
        if(a>b) swap(a,b);
        if(a==(int)floor((b-a)*w)) printf("0\n");
        else {printf("1\n");
             fun(a,b);
        }
    }
}



取石子(九)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述

最近TopCoder的Yougth和Hrdv在玩一个游戏,游戏是这样的。

n堆石子,两个人轮流从其中某一堆中任意取走一定的石子,最后不能取的为赢家,注意: 每次只能从一堆取任意个,可以取完这堆,但不能不取。

假设Yougth先取,输入赢了的人名字、

输入
第一行输入n,代表有n组测试数据(n<=10000)
以下每组测试数据包含两行:第一行:包含一个整数m,代表本组测试数据有m(m<=1000)堆石子;
:第二行:包含m个整数Ai(Ai<=10000),分别代表第i堆石子的数量。
输出
若Yougth赢输出“Yougth”,否则输出“Hrdv”注意每组结果占一行。。
样例输入
3
2
1 1
3
3 8 11
2
5 10
样例输出
Yougth
Hrdv
Yougth


 
#include<stdio.h>
int main()
{
	int T,n,a,i,result,count;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		result=0;
		count=0;
		for(i=1;i<=n;i++)
		{
			scanf("%d",&a);
			result=result^a;
			if(a>1)
				count++;
		}
		if((count&&result)||(!count&&!result))
			printf("Yougth\n");
		else
			printf("Hrdv\n");
	}
	return 0;
}        


取石子(十)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 6
描述

不知不觉取石子已经到第十道了。地球上的石子也快要取完了!

开个玩笑,当然还是有很多石子的,取石子的题目也是做不完的,今天又来了一道!

有n堆石子,每一堆的规则如下:

第一堆每次只能取2的幂次(即:1,2,4,8,16…);

第二堆只能取菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量,斐波那契数即后面的数是前面两个数的和);

第三堆可以取任意多个,最小1个,最多可以把这一堆石子取完;

第四堆只能取1和偶数个(即:1,2,4,6,8,10...);

第五堆只能取奇数个(即:1,3,5,7,9.....);

好吧,这样下去太烦人了,六堆及其以后每堆最多取的数量为堆数编号,即第六堆只能取(1,2,3,4,5,6),第七堆只能取(1,2,3,4,5,6,7)....

别看规则很多,但Yougth和Hrdv都是聪明人,现在由Yougth先取,比赛规定谁先取完所有石子既为胜者,输出胜者的名字。

输入
有多组测试数据,每组测试数据开始有一个n。
后面有n个数代表每一堆石子的数量,当n为0是表示输入结束。(所有数据小于1000)
输出
假如Yougth赢输出“Yougth”,Hrdv赢输出“Hrdv”。
样例输入
6
2 4 2 3 6 7 
样例输出
Hrdv

具体分析见博客:博弈-组合游戏 中的讲解


#include <iostream>
#include <string.h>
#include <cmath>
using namespace std;
#define maxn 1010
int a[maxn];
int sg[maxn];
void fun1(){
    //int sg[maxn];
    int vis[maxn];
    int a[maxn];
    a[1]=1;a[2]=2;
    for(int i=3;i<maxn;i++)
        a[i]=a[i-1]+a[i-2];
    sg[0]=0;
    for(int i=1;i<maxn;i++){
        memset(vis,0,sizeof(vis));
        for(int j=1;a[j]<=i;j++){vis[sg[i-a[j]]]=1;}
        for(int j=0;;j++){
            if(!vis[j]){
               sg[i]=j;
                break;
            }
        }
    }


}

int fun(int i,int n){
    if(n==1) return i%3;
    else if(n==2){
            if(i==0)
             return 0;
        // int bb[35]={1 ,2 ,3 ,0 ,1 ,2 ,3, 4 ,5 ,0 ,1 ,2 ,3 ,0 ,1 ,2 ,3 ,4 ,5 ,0 ,1 ,2 ,3 ,0 ,1 ,2 ,3 ,4 ,5 ,0 ,1 ,2 ,3 ,4 ,5};
        // return bb[(i-1)%35];
        return sg[i];
    }else if(n==3){
        return i;
    }else if(n==4){
        if(i==0)
          return 0;
        if(i==1 || i==2)
            return i;
        else
            return ((i-2)/6)*3+(i-2)%6-1;
    }else if(n==5){
        return (i)%2;
    }else{
        return (i)%(n+1);
    }
}
int main(){
    fun1();
    int n;
    while(cin>>n,n){
        for(int i=0;i<n;i++){
            cin>>a[i];
        }
        int sum=0;
        for(int i=0;i<n;i++){
            sum^=fun(a[i],i+1);
        }
        if(sum)cout<<"Yougth"<<endl;
        else cout<<"Hrdv"<<endl;
    }
return 0;}




Wythoff Game

时间限制: 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 <iostream>
#include <string.h>

using namespace std;
#define maxn 100010
int a[maxn];
int b[maxn*3];
int main(){
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    a[0]=a[1]=0;
    b[0]=1;
    int mi=1;
    for(int i=1;i<maxn;i++){
        a[i]=mi;
        b[mi]=1;
        b[mi+i]=1;
        for(int j=mi;j<maxn;j++){
            if(b[j] == 0){
                mi=j;
                break;}
        }
    }
    int n;
    while(cin>>n){
    for(int i=0;i<=n;i++){
        cout<<"("<<a[i]<<","<<a[i]+i<<")";
    }
    cout<<endl;
    }


return 0;}
        








  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值