zoukankan      html  css  js  c++  java
  • 浅析Nim游戏(洛谷P2197)

    首先我们看例题:P2197 nim游戏

    题目描述

    甲,乙两个人玩Nim取石子游戏。

    nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这n堆石子的数量,他想知道是否存在先手必胜的策略。

    输入输出格式

    输入格式:

    第一行一个整数T<=10,表示有T组数据

    接下来每两行是一组数据,第一行一个整数n,表示有n堆石子,n<=10000;

    第二行有n个数,表示每一堆石子的数量

    输出格式:

    共T行,如果对于这组数据存在先手必胜策略则输出"Yes",否则输出"No",不包含引号,每个单词一行。

    输入输出样例

    输入样例#1: 
    2
    2
    1 1
    2
    1 0
    输出样例#1: 
    No
    Yes

    讲解:
    本题就是最最经典的nim游戏了。nim游戏过程中面临的状态叫做局面,第一个行动的为先手,第二个行动的为后手。考虑两人无比聪明,则必败局面仅当该局面所能到达的局面均为必败局面时出现,而必胜局面仅当后续局面存在至少1个必胜局面时出现,显然nim游戏中1为必胜局面(因为拿走1就赢了)。显然,nim游戏是不存在平局的,只有先手必赢或先手必输两种情况。

     

    定理:设各堆为a1、a2…an,则nim游戏先手必赢仅当a1 Xor a2 Xor…Xor an≠0.

    证明:

      首先,当石子均被取完时,则a数组都为0,存在a1 Xor a2 Xor…Xor an=0,因为每次取都会使石子数减少,当前局面若a1 Xor a2 Xor…Xor an≠0,我们只要保证能在取走一些石子后使得a1 Xor a2 Xor…Xor an=0,则必然保证自己能取走最后一个石子获得胜利。

      等价于证明:

      (1)当a1 Xor a2 Xor…Xor an≠0时,存在某种取法使得剩下的石子xor和为0。

      (2)当a1 Xor a2 Xor…Xor an=0时,不存在取法使得剩下的石子xor和为0。(即取走一些石子后必定Xor和不为0)

      首先证明(1),对于任何一个局面a1 Xor a2 Xor…Xor an=x≠0,设x的二进制最高位的1在第k位,则至少存在一堆石子ai的二进制第k位是1(因为我们是Xor运算,某一位上的1不会凭空出现)且ai≥x。由Xor运算法则知:x Xor ai<ai,(因为至少会使第k为上的1变为0)。于是我们从ai这堆里取走一些石子,使得ai堆剩下的石子数变为ai Xor x,此时再对剩下的各堆进行上述运算:a1 Xor a2 Xor…ai Xor x…Xor an=x Xor x=0,此时Xor和为0。 于是得证(1)。

      再来证明(2),对于任何一个局面a1 Xor a2 Xor…Xor ai Xor…an=0,我们反证:假设取走ai堆中的一些石子使ai变为了x,使得a1 Xor a2 Xor…Xor x Xor…an≠0,则显然是不可能的,因为开始Xor和就为0再由Xor运算的性质当ai变为x后若Xor和为0,当且仅当ai=x时成立。而nim游戏中不能不取,所以若当前局面Xor和为0,则必然会使下一局面Xor和不为0。于是(2)得证。

    结论:nim游戏只要满足先手的Xor和不为0,则先手必赢,否则先手必输。

    代码:

    #include<bits/stdc++.h>
    #define il inline
    #define ll long long
    using namespace std;
    il ll gi()
    {
        ll a=0;char x=getchar();bool f=0;
        while((x<'0'||x>'9')&&x!='-')x=getchar();
        if(x=='-')x=getchar(),f=1;
        while(x>='0'&&x<='9')a=a*10+x-48,x=getchar();
        return f?-a:a;
    }
    ll t,n,a[100005];
    int main()
    {
        t=gi();
        while(t--){
            n=gi();ll x=0;
            for(int i=1;i<=n;i++)a[i]=gi(),x^=a[i];
            if(x)puts("Yes");
            else puts("No");
        }
        return 0;
    }
  • 相关阅读:
    富文本
    frame,bounds,position,anchorPoint理解
    内存相关
    OpenGL
    Xcode 编译选项详解
    iOS 工程引用
    iOS 事件传递和消息处理
    UICollectionView用法
    数据库
    多线程
  • 原文地址:https://www.cnblogs.com/five20/p/8570667.html
Copyright © 2011-2022 走看看