通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

 

结论:(不给证明,可去搜证明)

对于一个Nim游戏的局面(a1,a2,...,an),它是必败态当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。怎么样,是不是很神奇?我看到它的时候也觉得很神奇,完全没有道理的和异或运算扯上了关系。但这个定理的证明却也不复杂,基本上就是按照两种position的证明来的。

 

int N,A[maxn]
void solve( )
{
    int x = 0;
    for(int i=0 ; i<N ; i++)
    x^=A[i];
    if(x!=0)
    puts("A");
    else 
    puts("B");
    return 0;
}

 现在来实战分析Nim

poj 1704 

题意】

从左到右有一排石子,给出石子所在的位置。规定每个石子只能向左移动,且不能跨过前面的石子。最左边的石子最多只能移动到1位置。每次选择一个石子按规则向左移动,问先手是否能赢。

 

分析:如果我们将棋子两两看成一对,之间的距离表示Nim里面小石头堆的数量,那这就是个Nim.那这里就有个疑问了,那奇数的时候怎么办了呐? 很简单拿第一个棋子的距离与 1 当成是一个堆其他的不变就好了,那问题又来了,为什么,可以这样转换呢? 这个道理很简单我棋子向左移动,是不是距离就减少了,那不就是相当于拿走石头吗,等等!聪明仔可能发现了问题了,一个棋子左移动,那肯定是有一堆的石头的数量增加了呀 , 这不符合Nim呀!想想也是,不过如果我们将其看做是对手不讲道义,破坏规则,进行加石头 。 那我们只要将对手增加石头的部分减回去,不就是原来的状态了吗。神奇不,注意需要排序,超坑

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 1000 ;
int n,p[maxn],val[maxn];
int main( )
{
    int t;
    scanf("%d",&t);
    while(t--)
    {

    scanf("%d",&n);
    for(int i=1 ; i<=n ; i++)
    scanf("%d",&p[i]) ;
    sort(p+1,p+1+n);
    int cnt=0;
    if(n%2==0)
    {
        for(int i=2 ; i<=n ; i+=2)
        {
            val[cnt++]=p[i]-p[i-1]-1;
        }
    }
    else
    {
        val[cnt++]=p[1]-1;
        for(int i=3 ; i<=n ; i+=2)
        {
            val[cnt++]=p[i]-p[i-1]-1;
        }
    }
    int x=0;
    for(int i=0 ; i<cnt ; i++)
    {
        x^=val[i];
        //printf("%d ",val[i]);
    }

     if(x!=0)
        puts("Georgia will win");
     else
        puts("Bob will win");
    }
     return 0;
}
View Code

 

题目:

A 和 B 在玩游戏,给k个数字a1 , a2 , ... , ak ; 一开始有n堆硬币 , 每一堆有 xi 枚 。 A 和 B 轮流选出一堆硬币 ,从中取出硬币 , 每次所取硬币的枚数一定在a1 , a2 , a3 ,...., ak ; 里面,A先取,取光硬币的一方获胜 。 

 

分析:这里引入一个概念 Grundy , 利用这个东西,很多游戏都可以转换为Nim>

只有一堆石头的情况

int grundy(int x){
    集合S={};
    for(j=1:k){
        if(a_j<=x) 将grundy(x-a_j)加到S集合中 
    } 
    return 最小的不属于S的非负整数 
}

rundy值:除(任意一步所能转移到 的状态  的Grundy值 )以外的最小非负整数,这样的Grundy值,和Nim中的一个石子堆类似,有如下性质:

mex{0,1,2}=3;mex{ 1, 2}=0 ; mex{ 2, 3}=1

1.Nim中有x颗石子的石子堆,能转移成有0,1,……,x-1堆石子的石子堆

2.从Grundy值为x的状态出发,可以转移到Grundy值为0,1,……,x-1的状态。 

 

Nim:

所有石子堆的石子数xi的XOR

x1 XOR x2 XOR …XOR xk

为0必败,为1必胜

 

Grundy值等价于Nim中石子数,所以对于Grundy的情况:

所有硬币堆的Grundy的值

grundy(x1)  XOR  grundy(x2)  XOR …… XOR grundy(xn)

 为0必败,为1必胜。

#include <cstdio>
#include <set>
#include <algorithm>
#define maxn 105
#define maxk 105
using namespace std;
int N,K,X[maxn],A[maxk];
int grundy[maxn+1];
 
 
void solve(){
    //轮到自己剩0的时候必败 
    grundy[0]=0;            
    
    //计算grundy 
    int max_x=*max_element(X,X+N);//找最大值
    for(int j=1;j<=max_x;j++){
        set<int> s;                //存储当前的状态 
        for(int i=0;i<K;i++){
            if(A[i]<=j) s.insert(grundy[j-A[i]]);
        }
        int g=0;                //寻找当前状态的最小排斥 
        while(s.count(g)!=0) g++;
        grundy[j]=g;
    }    
    int ans=0;
    for(int i=0;i<N;i++) ans^grundy[X[i]];
    
    if(ans!=0)    puts("Alice");
    else puts("Bob");
    
}
 
 
int main(){
    scanf("%d%d",&N,&K);    
    for(int i=0;i<N;i++){
        scanf("%d",&A[i]);
    }
    for(int j=0;j<K;j++){
        scanf("%d",&X[j]);
    }
    solve();
    
    return 0;
} 
 
View Code

相关文章:

  • 2022-02-24
  • 2021-12-04
  • 2021-12-03
  • 2021-11-24
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-06-30
  • 2022-12-23
  • 2021-10-06
  • 2022-02-14
  • 2021-12-04
  • 2021-07-19
相关资源
相似解决方案