• P1247 取火柴游戏


    原题链接  https://www.luogu.com.cn/problem/P1247

    题解 

    这是一道经典的 NIM游戏 的博弈论 。

    NIM游戏

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

    设有 n 堆石子,每堆石子的数量为 a1 , a2 , …… , an

    当某个人面临 a1 = a= …… = an = 0 的状态时,这个人就输了,所以我们将此状态称为 “ 必败状态 ”

    如果一个状态能通向必败状态,那么就称当前状态为 “ 必胜状态 ”,因为两个人都是绝顶聪明的,能赢绝不输;

    我们先从最简单的必败状态开始分析:

    若有 a1 = a= …… = an = 0,那么可以得到 a1 ^ a2 ^…… ^ an = 0;

    话说虽然 a1 ^ a^…… ^ an = 0 不能推得 a1 = a= …… = an = 0,但是你若想让对手面临 a1 = a= …… = an = 0 的状态,你起码要保证把 a1 ^ a^…… ^ an = 0 的状态留给对手好吧;

    分析一下将 a1 ^ a^…… ^ an = 0 的状态留给对手的结果:

    ①. a1 = a= …… = an = 0

    皆大欢喜,你赢了!

    ②. 有 i 对相等的数(1 <= i <= n/2)

    虽然你现在没有赢,但是对手肯定赢不了,因为此时至少有两堆石子没有被取空,你的对手不可能同时将两堆石子取空;

    这么一看,你若把 a1 ^ a^…… ^ an = 0 的状态留给对面,除了能把对手恶心死好像就没有什么坏处了,何乐而不为呢?

    但是你现在的状态是 a1 ^ a^…… ^ an = x 啊,那要怎么办呢?

    根据异或的性质可以得到:a1 ^ a^…… ^ an ^ x = 0,如果我们对其中一堆石子 ai 取得只剩下 ai' = ai ^ x 个,那么此时留给对手的状态就是 a1 ^ a^… ^ ai' ^ … ^ an = 0 了 。

    通过上述操作可知,若对手面临 a1 ^ a^…… ^ an = 0 的状态,无论ta接下来怎么取,一定会将状态转化为 a1 ^ a^…^ ai' ^… ^ an = y ( y≠0 ) ,然后我们可以反手再将状态变为 a1 ^ a^…^ ai' ^…^ aj' ^… ^ an = 0 ( aj' = aj ^ y ) ,这样对手就一直面临着 a1 ^ a^…… ^ an = 0 的状态,也就自然不会有机会赢了 。

    总结:a1 ^ a^…… ^ an = x 是一个必胜状态;反之,a1 ^ a^…… ^ an = 0 是一个必败状态(两种状态的胜负情况都是对于当前操作者来说的)。

    还有最后一个问题:当面临 a1 ^ a^…… ^ an = x 的状态时,我是将哪一堆的 ai 取成 ai' = ai ^ x 呢?

    看到题目中给出的游戏规则:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不允许跨堆取,也不允许不取

    所以我们要保证 ai' < ai ,即 ai ^ x < ai

    设 x 在二进制表示下最高位的 1 是在第 k 位,由异或的性质可知:a1 ~ an 的若干数中(至少一个)的第 k 位为 1,然后我们随便从中找出一个数,设为 ai

    思考一下 ai ^ x < ai ?我们对 ai 和 x 的二进制位进行分析:

    由于 x 的最高位在第 k 位,所以第 k 位往左的部分异或后不变; 第 k 位异或后为 0;

    第 k 位由 1 变为了 0,ai 的大小减去 2k-1 ,就算后面的 1~k-1 位由 0000……000 变成了 1111……111,ai 的大小最多增加 20 + 21 + …… + 2k-2 = 2k-1 - 1 。

    这么看来,ai 的值注定是要减小的了,那么 ai' < ai ,则 ai 就是我们要找的天选之子。

    Code: 

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=500005;
    int n;
    int a[N];
    long long ans;
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) 
        {
            scanf("%d",&a[i]);
            ans^=a[i];                  //算出每堆石子数的异或和 
        }
        if(ans==0) printf("lose
    ");    //ans==0是必败状态 
        else                            //否则为必胜状态 
        {                               //我们接下来的任务就是要找一个ai把它取成ai^ans 
            int x=0;
            for(int i=45;i;i--)         //寻找ans的最高位的1是第几位 
            {
                if(ans&(1ll<<i)) 
                {
                    x=i;
                    break;              //由于我们是从高位向低位枚举,所以一旦找到,那么肯定是最高位,直接跳出 
                }
            }
            for(int i=1;i<=n;i++)       //我们再从所有的石子堆里找出一个ai,使得ai的第x位也是1 
            {
                if(a[i]&(1ll<<x))       //找到符合条件的ai 
                {    
                    printf("%d %d
    ",a[i]-(a[i]^ans),i);  //将第i堆石子取得只剩下ai^ans,那就要取走ai-(ai^ans) 
                    a[i]=a[i]^ans;      //剩下ai^ans 
                    for(int j=1;j<=n;j++) printf("%d ",a[j]);  //将剩下每堆得石子数输出即可 
                    return 0;        
                }
            }
        }
        return 0;
    }
  • 相关阅读:
    (OK) port_lighttpd_to_Android——没有基于android 4.4源码
    Linux添加头文件路径—INCLUDE_PATH
    (OK) 交叉编译hello.c for android (--sysroot),不使用Android.mk和ndk-build
    Building and running Node.js for Android
    编译node-v4.2.1,出现错误:undefined reference to getpwuid_r
    我为什么向后端工程师推荐Node.js
    (OK) 编译 pcre-8.37 静态库
    port_lighttpd_to_Android——基于android 4.4源码
    深受C/C++程序员欢迎的11款IDE
    推荐!国外程序员整理的 PHP 资源大全
  • 原文地址:https://www.cnblogs.com/xcg123/p/13121397.html
Copyright © 2020-2023  润新知