CQOI2013 新nim游戏

CQOI2013 新nim游戏

1 题目描述

  • 传统的 Nim 游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。

    本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和 Nim 游戏一样。

    如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。

2 分析

  • 首先我们先来证明一下nim游戏的结论,在nim游戏里面,每次可以从一堆石子里面取走任意数量的石子,谁取走了最后的石子,谁就获胜。这个游戏的结论是石子的数量\(x_i\)异或起来的时候等于0的时候先手必败,如果是非0,那么先手一定有必胜策略。我们来简单证明一下,假设当前有n堆石子,每堆石子的数量是\(x_i\),如果当前异或起来是0,那么我们无论怎么操作,都会使得剩余的异或结果为非0。我们可以假设有其中任意n-1堆石子的异或结果是K,那么还留下的一堆石子一定也是K,此时我们只要随便修改一下这堆石子的数量变成K',那么这n堆石子异或的结果就不是0了,这样我们就证明了异或为0是一个必败局面。接下来我们假设当前n堆石子异或不为0,我们总能找到一堆石子的数量为K',使得K'>其他n-1堆石子的异或值,此时我们减少K'变成和其他n-1堆的异或值相同,这样n堆石子的异或就为0了,这样我们证明了异或为非0是必胜局面。
  • 本题中,前两次规则和普通的nim游戏不一样,仍旧要使得先手必胜。 前两回合可以一堆不拿,也可以拿走若干堆(不能全部拿走)。 本来我们可以第一个人拿走了以后,留下异或为0的局面,但是现在不行。因为即使现在异或为0,第2个人仍旧可以拿走若干堆使得异或仍旧是0,这样先手就败了。如果第一个人留下异或不为0的情况,那么第2个人由于只能整堆拿,那么留下的局面异或肯定还是非0,这样先手就必胜了。
  • 所以我们只要留下一个异或不为0的情况就可以了,但是如果这些堆里面去掉若干会变成异或0就不行。也就是第一个人要留下的堆必须是不能相互表示的,这样就是线性基了。我们要先排序(按照火柴数量从大到小),然后依次贪心插入线性基里面,这样的得到就是最大的,去掉的就是最小的。
  • 时间复杂度:\(O(k\log_{2}{k}+30*k)\),前者是排序的时间复杂度,后者是插入线性基的时间复杂度。

3 代码

#include<bits/stdc++.h>
using namespace std; 
#define N 110 
#define ll long long 
int d[N],n,a[N];  
ll sum,ans; 
int ins(int  x){
    for(int i=30;i>=0;i--)  
        if(x&(1<<i)){
            if(d[i]) x^=d[i];  
            else{
                d[i]=x;  
                return 1; 
            }
        }
    return 0;  
}
int main(){
    scanf("%d",&n);  
    for(int i=1;i<=n;i++) 
        scanf("%d",&a[i]),sum+=a[i];  
    for(int i=1;i<n;i++) 
        for(int j=i+1;j<=n;j++) 
            if(a[i]<a[j])
                swap(a[i],a[j]); 
    for(int i=1;i<=n;i++)  
        if(ins(a[i])) ans+=a[i];  
    cout<<sum-ans<<endl; 
    return 0;     
}
posted @ 2020-06-11 21:20  zjxxcn  阅读(191)  评论(0编辑  收藏  举报