229. 新NIM游戏
题目链接
229. 新NIM游戏
传统的 Nim 游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。
两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。
可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。
拿走最后一根火柴的游戏者胜利。
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。
可以一堆都不拿,但不可以全部拿走。
第二回合也一样,第二个游戏者也有这样一次机会。
从第三个回合(又轮到第一个游戏者)开始,规则和 Nim 游戏一样。
如果你先拿,怎样才能保证获胜?
如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
输入格式
第一行为整数 \(k\),即火柴堆数。
第二行包含 \(k\) 个正整数(均不超过 \(10^9\)),即各堆的火柴个数。
输出格式
输出第一回合拿的火柴数目的最小值。
如果不能保证取胜,输出 \(-1\)。
数据范围
\(1 \le k \le 100\)
输入样例:
6
5 5 6 6 5 5
输出样例:
21
解题思路
线性基,博弈论
考虑一般的 \(nim\) 游戏,先手必胜局面: \(a_1\bigoplus a_2\bigoplus\dots a_n\neq0\)
故先手需要选取若干子集使后手无论选取哪些子集使先手都能面临必胜局面,即先手选取若干子集后剩余子集的任意子集的异或和都不为 \(0\),线性基正有此性质,即由于线性基是极大线性无关组,其任意子集的异或和不为 \(0\),故选择尽量小的子集和,使剩余子集为线性基,构成线性基的子集和最大
直观上,逆序排序构建线性基即可满足要求,\(\color{red}{为什么?}\)首先第一个最大元素必然是线性基元素,现在假设第 \(k\) 个元素为线性基元素,如果第 \(k+1\) 个元素应该是线性基元素但没有作为线性基元素,则其表示线性空间则可能不能表示原线性空间,即使后面有元素能够取代该元素表示原空间,由于是逆序排序要求线性基元素和越大越好,同时由于线性基元素个数是一定的,这样不选第 \(k+1\) 个元素作为线性基元素会更劣
- 时间复杂度:\(O(n(30+logn))\)
代码
// Problem: 新NIM游戏
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/231/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=105;
int k,a[N],b[N];
LL res;
bool insert(int x)
{
for(int i=29;i>=0;i--)
if(x>>i&1)
{
if(!b[i])
{
b[i]=x;
return true;
}
else
x^=b[i];
}
return x;
}
int main()
{
help;
cin>>k;
for(int i=0;i<k;i++)cin>>a[i],res+=a[i];
sort(a,a+k);
for(int i=k-1;i>=0;i--)
if(insert(a[i]))res-=a[i];
cout<<res;
return 0;
}