博弈-取石子

取石子(一)

時間限制: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
描述
最近TopCoderPIAOYIHRDV很無聊,於是就想了一個遊戲,遊戲是這樣的:有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;}
        








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章