传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
Input
第一行为整数k。即火柴堆数。第二行包含k个不超过109的正整数,即各堆的火柴个数。
Output
输出第一回合拿的火柴数目的最小值。如果不能保证取胜,输出-1。
Sample Input
6
5 5 6 6 5 5
Sample Output
21
Hint
k<=100
思路:
我们知道NIM博弈的必胜态是异或和不为0。
对于数集,线性基是异或和不为0且数目最少的子集。
只要先手取完剩下的是线性基,非线性基部分就是先手取的。那么后手取完,剩下的数目异或和一定不为0了,那么先手进行NIM游戏的时候就是必胜态了。
因为题目要求最小,那么从大到小开始插入。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int a[105],d[105];
int insert(int x)
{
for(int i = 31;i >= 0;i--)
{
if((x >> i) & 1)
{
if(d[i])x ^= d[i];
else
{
d[i] = x;
return true;
}
}
}
return false;
}
int main()
{
int n;scanf("%d",&n);
ll res = 0;
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
sort(a + 1,a + 1 + n);
for(int i = n;i >= 1;i--)
{
if(!insert(a[i]))res += a[i];
}
printf("%lld\n",res);
return 0;
}