zoukankan      html  css  js  c++  java
  • P2197 nim游戏

    传送门

    经典的博弈

    如果只有两堆

    那么结束状态就是(0,0)

    考虑先手

    怎样保持后手面对(0,0)

    如果两堆大小不一样

    那么先手只要保持两堆一样大就行了

    先手先取大的一堆

    使两堆一样大

    后手无论取多少

    先手只要在另一堆取一样多

    最后就一定是后手面对(0,0)

    但是如果两堆一样多...

    那先手取完

    就变成两堆不一样多,后手先取了

    那么先手必输

    那么对于很多堆的情况

    先手也要尽量使后手面对结束状态

    有一个结论:

    如果每堆的数量的异或和(xor,C语言中是" ^ ")不为 0

    那么先手必胜,反之先手必输

    证明:


    1. 对于总异或和 k ,如果不为 0

    对于 k 在2进制下的最高位 i 的 1

    肯定至少有一个原数 N 在2进制下的第 i 位为 1 (不然就不可能使 k 在第 i 位为1,原因请见 异或的定义

    所以如果我们要让 k 异或一个数 x ,使 k 变为 0,那么显然当x = k时是可以的

    所以如果我们把 N 成 N^k, 那么总异或和就变成 0 了(显然总异或和会变成 k^k = 0)

    显然N^k 一定是小于 N 的

    因为 N 和 k 的最高位 i 都为 1

    异或一下第 i 位就变成 0 了

    相当于变小了

    所以 N^k 一定小于 N

    所以只要把 N 减某个数就一定能变成 N^k;

    2. 如果 总异或和为 k 为 0

    那么如果你把任何一个堆的数减小任何一个数都会导致 k 变成不为 0 的数

    证明也不难

    把一个数减小,那么它至少其中一位会改变

    如果有一位改变了,那么异或和肯定也改变了(原因请见 异或的定义,稍微想一想就懂了)

    那异或和就不是 0 了;

    好了有了以上两点就容易了

    对于先手,如果异或和 k 不为 0

    那就把 k 变成 0

    那么后手就只能把 k 变成一个不为 0 的数

    那先手就再把 k 变为 0

    一直减下去

    当所有数都减完了

    k=0,肯定是后手面对这种状态

    所以先手必胜

    反之

    如果一开始异或和 k 为 0...

    那么先手只能让 k 变成不为 0 的数

    然后后手笑了...

    先手就必输了


    证明完毕

    有了结论就很简单了

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int t,n,a,k;
    int main()
    {
        cin>>t;
        while(t--)
        {
            k=0;
            cin>>n;
            for(int i=1;i<=n;i++)
            {
                cin>>a;
                k^=a;
            }
            if(k) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
  • 相关阅读:
    Web端导出CSV
    dojo/dom-style样式操作学习笔记
    dojo/dom源码学习
    上层建筑——DOM元素的特性与属性(dojo/dom-prop)
    上层建筑——DOM元素的特性与属性(dojo/dom-attr)
    返本求源——DOM元素的特性与属性
    DOM扩展札记
    以代码爱好者角度来看AMD与CMD
    dojo事件驱动编程之事件绑定
    通过Web.config中的configSections配置自己系统的全局常量
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9560201.html
Copyright © 2011-2022 走看看