BZOJ3105 [cqoi2013]新Nim游戏

题意

F.A.Qs Home Discuss ProblemSet Status Ranklist Contest 入门OJ ModifyUser   autoint Logout 捐赠本站
Problem 3105. -- [cqoi2013]新Nim游戏

3105: [cqoi2013]新Nim游戏

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 1941   Solved: 1171
[ Submit][ Status][ Discuss]

Description

传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
 

Input

第一行为整数k。即火柴堆数。第二行包含k个不超过109的正整数,即各堆的火柴个数。
 

Output

 
输出第一回合拿的火柴数目的最小值。如果不能保证取胜,输出-1。

Sample Input

6
5 5 6 6 5 5

Sample Output

21

HINT

k<=100

Source

[ Submit][ Status][ Discuss]

HOME Back

分析

参照wyfcyx_forever的题解。

我们第一次拿完后,要使得剩下的火柴中不存在异或和为0的子集,否则对方会将先手必败的状态留给我们。

因此我们需要寻求极大的线性无关组,答案即为总和减去极大线性无关组的权值和。

显然存在线性无关组,因此必然存在解。

那么如何求解极大线性无关组呢?

我们能够证明这是一个拟阵,因此只需要从大到小排序,依次贪心的添加到当前集合就可以了。

有关拟阵的证明:
我们设n个火柴堆的数目为集合S,若某个S的子集r不存在任何一个非空子集异或和0,则r∈I.下面我们证明二元组M=(S,I)是一个拟阵。
遗传性:设A∈I,则A是S的线性无关组,则A的任意非空子集均线性无关,即对A的任意子集B,B均线性无关,因此B∈I,证毕。
交换性:设A,B∈I,且|A|<|B|,我们要证明存在x∈B,使得A∪{x}∈I.利用反证法,假设对于任意x∈B-A,均有A∪{x}不属于I,则B-A中的元素均在A的异或空间中,可由A的子集异或和表示。
因此B中的元素都在A的异或空间中。那么必然有B的异或空间包含于A的异或空间。由|A|<|B|且A,B线性无关,显然矛盾。因此交换性存在,证毕。
从而我们可以使用贪心算法确定最优解。

因此我们采用在线维护线性基的方法判断当前的数能否加入集合。

时间复杂度\(O(k \log v)\),我猜出题人想让我们打高斯消元

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
    while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;

co int N=101;
int k,a[N],b[N];
ll ans;
int main(){
//  freopen(".in","r",stdin),freopen(".out","w",stdout);
    read(k);
    for(int i=1;i<=k;++i) ans+=read(a[i]);
    std::sort(a+1,a+k+1);
    for(int i=k,x;i;--i){
        x=a[i];
        for(int j=30;j>=0;--j)if(a[i]>>j){
            if(!b[j]) {b[j]=a[i];break;}
            a[i]^=b[j];
        }
        if(a[i]) ans-=x;
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10659627.html