点点滴滴”

导航

博弈知识点

http://blog.csdn.net/acm_cxlove/article/details/7854526

 简单的巴什博弈

1:HDOJ1846 Brave Game [找规律]  

 

 #include <iostream>
 #include <string.h>
 #include <stdio.h>

 using namespace std;

 int main()
 {
     int t;
     int n,m;
     scanf("%d",&t);
     while(t--)
     {
         scanf("%d%d",&n,&m);
         if(n%(m+1)==0)
         printf("second\n");
         else
         printf("first\n");
     }
     return 0;
 }

   HDOJ2149 Public Sale [巴什博弈]输出走法

 #include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;

int main()
{
    int n,m;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        if(n>=m)
        {
            printf("%d",m);
            for(int i=m+1; i<=n; i++)
                printf(" %d",i);
            printf("\n");
        }
        else
        {
            if(m%(n+1)==0)
                printf("none\n");
            else
                printf("%d\n",m%(n+1));
        }
    }
    return 0;

}

 威佐夫博弈

HDOJ1527&POJ1067 取石子游戏

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>

using namespace std;

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n<m)
            swap(n,m);
        int k=n-m;
        double r=((1+sqrt(5))/2.0);
        if(m!=(int)(k*r))  ///相等时就是先取者的奇异局面  就是输的状态
printf(
"1\n"); else printf("0\n"); } return 0; }

 HDU 2177  较上次来说  这个让打印路径了~~~~~~~~~~

/*
由于要输出方案,便 得复杂了。数据不是很大,首先打表,所有whthoff 的奇异局势。

然后直接判断是否为必胜局面。、

如果必胜,首先判断能否直接同时相减得到。这里不需要遍历或者二分查找。由于两者同时减去一个数,他们的差不变,而且ak=k*(sqrt(5)+1),bk=ak+k;

则可以通过二者的差直接定位,然后判断。

对于另外一种情况,其中一个减去某个数,得到奇异局势,则是分情况二分查找。

注意一些细节
*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<string>
#include<map>
#define LL long long
#define N 1000000
#define inf 1<<20
using namespace std;
int a[500000],b[500000],cnt;
void Search1(int n,int m){
    int low=0,high=cnt,mid;
    while(low<=high){
        mid=(high+low)>>1;
        if(n==a[mid]){
            if(m>b[mid])
                printf("%d %d\n",a[mid],b[mid]);
            return;
        }
        if(a[mid]<n)
            low=mid+1;
        else
            high=mid-1;
    }
}
void Search2(int n,int m){
    int low=0,high=cnt,mid;
    while(low<=high){
        mid=(high+low)>>1;
        if(n==b[mid]){
            if(m>a[mid])
                printf("%d %d\n",a[mid],b[mid]);
            return;
        }
        if(b[mid]<n)
            low=mid+1;
        else
            high=mid-1;
    }
}
int main(){
    for(int i=0;i<500000;i++){
        a[i]=(int)(i*(sqrt(5.0)+1)/2);
        b[i]=a[i]+i;
        if(b[i]>=1000000){
            cnt=i;
            break;
        }
    }
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF&&n+m){
        if(n>m)
            swap(n,m);
        if(n==(int)((sqrt(5.0)+1)*(m-n)/2))
            printf("0\n");
        else{
            printf("1\n");
            if(m-n<cnt&&n-a[m-n]==m-b[m-n])
                printf("%d %d\n",a[m-n],b[m-n]);
            Search1(n,m);
            if(n!=m)
                Search1(m,n);
            Search2(n,m);
            if(n!=m)
                Search2(m,n);
        }
    }

    return 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;
}

 尼姆博奕

 HDU1907

/*
cnt == 0:说明每一种颜色都只有一个,相当于一堆石子,每次必须取一个的情况
Sg(x) = x % 2。
本题题义稍有不同,取走最后一个时是输,所以x = n - 1
如果cnt != 0,题义的改变对结果影响
*/
#include <iostream>  
using namespace std;  
int value[101];  
int main ()  
{  
    int n,sum,temp, t, i, cnt; 
    cin>>t;
    while (t--)  
    {
        cin>>n;
        sum = 0;cnt = 0;
        for(i = 0; i < n; i++)
        {
            cin>>temp;
            sum = sum^temp;
            if(temp > 1)
                cnt = 1;
        }
        if(cnt == 0)
            sum = ( (n-1) % 2 );
        if(sum == 0)
            cout<<"Brother"<<endl;
        else
            cout<<"John"<<endl;
    }  
    return 0;  
}

#include<iostream>
using namespace std;
int main()
{
    int i,n,m;
    int t;
    cin>>t;
    while(t--)
    {
        cin >> n;
        int flag = 0; //判断是否是孤单堆
        int s = 0;
        for(i = 0; i < n; i++)
        {
            cin >> m;
            s ^= m;
            if(m > 1)
                flag = 1;
        }
        if(flag == 0)
            if(n % 2)
                cout <<"Brother"<<endl;
            else
                cout <<"John"<<endl;
        else if(s == 0)
            cout << "Brother" <<endl;
        else
            cout << "John" <<endl;
    }
    return 0;
}

 

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int main()
{
    int t,n,i;
    int sum1,sum2,ans;
    int a[55];
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        sum1 = sum2 = ans = 0;
        for(i = 0;i<n;i++)
        {
            scanf("%d",&a[i]);
            ans^=a[i];
            if(a[i]>=2)
            sum2++;
            else
            sum1++;
        }
        if((ans && sum2) || (!ans && !sum2))
        printf("John\n");
        if((ans && sum1%2 && !sum2) || (!ans && sum2>=2))
        printf("Brother\n");
    }

    return 0;
}

 HDU2509

Nim博弈变形(anti-nim)

题目大意:有n堆苹果,每堆有mi个。两人轮流取,每次可以从一堆苹果中取任意连续个苹果,最后取光者为输。Fra先下,问是否可以获胜。

算法如下:

这题与以往的博弈题的胜负条件不同,谁先走完最后一步谁输,但他也是一类Nim游戏,即为anti-nim游戏。

首先给出结论:先手胜当且仅当 ①所有堆石子数都为1且游戏的SG值为0(即有偶数个孤单堆-每堆只有1个石子数);②存在某堆石子数大于1且游戏的SG值不为0.

证明

  1. 若所有堆都为1SG值为0,则共有偶数堆石子,故先手胜。
  2. i)只有一堆石子数大于1时,我们总可以对该石子操作,使操作后堆数为奇数且所有堆的石子数均为1

    ii)有超过一堆的石子数1时,先手将SG值变为0即可,且总还存在某堆石子数大于1

因为先手胜。

 

此题用到的概念:

【定义1】:若一堆中仅有一个石子,则被称为孤单堆。若大于1个,则称为充裕堆

【定义2】:T态中,若充裕堆的堆数大于等于2,则称为完全利他态,用T2表示;若充裕堆的堆数等于0,则称为部分利他态。用T0表示。

孤单堆的根数异或智慧影响二进制的最后以为,但充裕堆会影响高位(非最后一位)。一个充裕堆,高位必有一位不为0,则所有根数异或不为0。故不会是T态。

【定理1】:S0态,即仅有奇数个孤单堆,必败。T0态必胜。

证明:S0态,其实就是每次只能取一根。每次第奇数根都由自己取,第偶数根都由对方取,所以最后一根必由自己取。所以必败。同理:T0态必胜。

【定理2】:S1态,只要方法正确,必胜。

证明:若此时孤单堆堆数为奇数,把充裕堆取完;否则,取成一根。这样,就变成奇数个孤单堆,由对方取。由定理1,对方必输,己必胜。

【定理3】:S2态不可转一次变为T0态。

证明:充裕堆数不可能一次由2变为0

【定理4】:S2态可一次转变为T2态。

证明:因为对于任何一个S态,总能从一堆中取出若干个使之成为T态。又因为S1态,只要方法正确,必胜。S2态不可转一次变为T0态,所以转变的T态为T2态。

【定理5】:T2态,只能转变为S2态或S1态。

证明:因为T态,取任何一堆的若干根都将成为S态。由于充裕堆不可能一次由2变为0,所以此时的S态不可能为S0态。得证。

【定理6】:S2态,只要方法正确,必胜。

证明:方法如下:

  1. S2态,就把它变为T2态。(定理4);
  2. 对方只能T2转变为S2态或S1态(定理5)。

    若转变为S2,则转向①。

    若转变为S1,这时己必胜(定理1)。

【定理7】:T2态必输。

证明:同定理6.

综上所述:必输态有:T2S0;必胜态有:S2S1T0

#include<iostream>
using namespace std;
int main()
{
    int i,n,m;
    while(cin >> n)
    {
        int flag = 0; //判断是否是孤单堆
        int s = 0;
        for(i = 0; i < n; i++)
        {
            cin >> m;
            s ^= m;
            if(m > 1)
                flag = 1;
        }
        if(flag == 0)
            if(n % 2)
                cout << "No\n";
            else
                cout << "Yes\n";
        else if(s == 0)
            cout << "No" <<endl;
        else
            cout << "Yes" <<endl;
    }
    return 0;
}

 Nim博弈

HDU5011

Game

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 260    Accepted Submission(s): 202


Problem Description
Here is a game for two players. The rule of the game is described below:

● In the beginning of the game, there are a lot of piles of beads.

● Players take turns to play. Each turn, player choose a pile i and remove some (at least one) beads from it. Then he could do nothing or split pile i into two piles with a beads and b beads.(a,b > 0 and a + b equals to the number of beads of pile i after removing)

● If after a player's turn, there is no beads left, the player is the winner.

Suppose that the two players are all very clever and they will use optimal game strategies. Your job is to tell whether the player who plays first can win the game.
 


Input
There are multiple test cases. Please process till EOF.

For each test case, the first line contains a postive integer n(n < 105) means there are n piles of beads. The next line contains n postive integer, the i-th postive integer ai(ai < 231) means there are ai beads in the i-th pile.
 


Output
For each test case, if the first player can win the game, ouput "Win" and if he can't, ouput "Lose"
 


Sample Input
1
1
2
1 1
3
1 2 3
 


Sample Output
Win
Lose
Lose
 #include <iostream>
 #include <string.h>
 #include <stdio.h>

 using namespace std;

 int main()
 {
     int n;
     int a[20000];
     while(scanf("%d",&n)!=EOF)
     {
         int sum=0;
         for(int i=0;i<n;i++)
         {
             scanf("%d",&a[i]);
             sum^=a[i];
         }
         if(sum)
         printf("Win\n");
         else
         printf("Lose\n");
     }
     return 0;
 }

 

 

HDU 2167

取(m堆)石子游戏

Time Limit: 1000 MS Memory Limit: 32768 KB

64-bit integer IO format: %I64d , %I64u Java class name: Main

[Submit] [Status] [Discuss]

Description

m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后 输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的 中那一堆取走7个剩下3个.

Input

输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.

Output

先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.

Sample Input

2
45 45
3
3 6 9
5
5 7 8 9 10
0

Sample Output

No
Yes
9 5
Yes
8 1
9 0
10 3
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <cmath>

using namespace std;

int main()
{
    int n;
    int m[200005];
    while(scanf("%d",&n),n)
    {

        int sum=0;
        for(int i=0;i<n;i++)
        {
          scanf("%d",&m[i]);
          sum=sum^m[i];
        }
        if(sum)
        {
             printf("Yes\n");
             int s=0;
             for(int i=0;i<n;i++)
             {
                 s=sum^m[i];
                 if(s<m[i])
                 printf("%d %d\n",m[i],s);
             }
        }

        else
        printf("No\n");
    }
    return 0;
}

 

 

posted on 2014-09-06 08:56  点点滴滴”  阅读(246)  评论(0编辑  收藏  举报