【洛谷P3812】【模板】线性基
题目
题目链接:https://www.luogu.com.cn/problem/P3812
给定 \(n\) 个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
思路
一个值域为 \([0,A]\) 的序列的线性基长度为 \(\lceil\log A\rceil\)。并且通过线性基内元素互相异或,可以得到原序列中每一个数字。
具体的,我们设 \(d\) 是 \(a\) 的线性基,那么 \(d_i\) 表示 \(a\) 中二进制最高位的 \(1\) 是第 \(i\) 位的一个数。假设我们要插入一个数 \(x\),那么我们就不断重复以下过程:
- 找到 \(x\) 的最高位的 \(1\),判断该位置 \(d\) 是否有数字。如果没有则赋值后直接 break。
- 否则设 \(x\) 最高位的 \(1\) 在位置 \(i\),那么让 \(x\gets x\ \mathrm{xor}\ d_i\)。
- 如果 \(x\) 为 \(0\) 则直接退出。
因为每次操作二后 \(x\) 最高位的 \(1\) 都会变成 \(0\),所以复杂度是 \(O(\log x)\) 的。
这题要求异或和最大,我们就从高位到低位枚举,如果此时的答案 \(res\) 第 \(i\) 位为 \(0\),那么就让 \(res\gets res\ \mathrm{xor}\ d_i\)。这样显然每次操作不会更劣。
时间复杂度 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=55;
int n;
ll x,d[N];
bool flag;
void insert(ll x)
{
for (int i=N-1;i>=0;i--)
if (x&(1LL<<i))
{
if (!d[i]) { d[i]=x; return; }
x^=d[i];
}
flag=1;
}
ll getmax()
{
ll ans=0;
for (int i=N-1;i>=0;i--)
if (!(ans&(1LL<<i))) ans^=d[i];
return ans;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&x);
insert(x);
}
printf("%lld",getmax());
return 0;
}