题解:[CQOI2013]新Nim游戏

Sol

题目大意

第一轮为自定义游戏,取走若干堆石子,第二轮开始为Nim的游戏
(所以一定要审清楚题目)

题解

想必大家都知道Nim的游戏吧,如果不清楚结论的话,笔者顺便把Nim的游戏的结论也讲一下吧,方便大家把Nim的游戏也切掉

若干堆石子,每堆石子有若干颗石子,请问什么情况是先手必胜?
(貌似这个跟数学和博弈没什么关系)

如果初始状态下每堆石子数异或起来不为0的话,则先手必胜,反之后者必胜

证明如下:
首先我们将每堆石子数换算为二进制,位数 i i 上为1我们则可以取走其2i个石子

1.若石子数异或和不为0,那么考虑如果先手能通过取石子数使石子数异或和为0的话,那么接下来要不就还存在石子数,要不就没有了,在没有的情况下先手获胜

2.若接下来还存在石子数,必定就还有位数上有1,又因为不能不取,所以在该堆取走若干颗石子后必将导致局面变为每堆石子数异或和不为0

3.接下来又变为第1步,继续考虑先手是否存在方案将石子数异或和为0,有一种简单想法,假设现在我们找到了在全局石堆上有一堆的石子位有最高位为1(就是别的石子堆没有这一位),我们将最高位取走1后,考虑次高位,此时可能有别的石堆这一位上有1了,那么如果不算此位的话,可能其余位异或起来不为0,那么就从之前那一位上借来一个1使得该位异或起来为1,之后位数照然

4.第3方案是一定存在的,也就是一定存在该方案,因为此时局面异或起来是不为0的!你一定存在方案把它变为异或为0,就是按位取1就好了(不存在一个1的局面都不取的情况,因为那样局面就会是一定为0的)

5.综上,在Nim的游戏中若石子数异或和不为0则先手必胜

然后不就变得很简单了?(讲了这么就还没讲到重点)

Q:对于异或和?想到了什么
A:报告!线性基

在本题中,不能给后手任何让石子数异或为0的机会,所以在插入前先询问该数是否会让石子异或和变为0,若会,则把这堆棋子给拿掉,否则加入线性基里,这样后手无论如何都不能让石子异或和变为0了!(因为线性基里无论怎么搞异或和都不为0若想观察更多优美性质戳我),然后先手就必胜了

值得吐嘈的是,先手一定会必胜,所以puts("-1")期望得分0

然后求最小值?Sort一遍就好了,按照权值从大到小排序,其实也是贪心的思想(若此处不懂,推荐一道好题[BeiJing2011]元素)

上代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int _=500;
inline int read()
{
    char ch='!';int z=1,num=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')z=-1,ch=getchar();
    while(ch<='9'&&ch>='0')num=(num<<3)+(num<<1)+ch-'0',ch=getchar();
    return z*num;
}
int K,a[_];
bool cmp(int A,int B){return A>B;}
struct LB
{
    ll a[65],b[65];
    LB ()
        {
            memset(a,0,sizeof(a));
            memset(b,0,sizeof(b));
        }
    void Insert(ll x)
        {
            for(int i=60;i>=0;--i)
            {
                if(x&(1<<i))
                {
                    if(!a[i])
                    {a[i]=x;break;}
                    x^=a[i];
                }
            }
        }
    bool Find(ll x)
        {
            for(int i=60;i>=0;--i)
            {
                if(x&(1<<i))
                {
                    if(!a[i])
                        break;
                    x^=a[i];
                }
            }
            return x>0;
        }
};
int main()
{
    K=read();
    for(int i=1;i<=K;++i)
        a[i]=read();
    sort(a+1,a+1+K,cmp);
    ll ans=0;LB S;
    for(int i=1;i<=K;++i)
    {
        if(S.Find(a[i]))
            S.Insert(a[i]);
        else ans+=a[i];
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值