线性基学习笔记
线性基
线性基大多数时候是用来解决异或相关的问题。这篇博客会介绍线性基最最基础的东西。
性质
引用自 oi-wiki 里对线性基的定义
线性基是向量空间的一组基,通常可以解决有关异或的一些题目。
通俗一点的讲法就是由一个集合构造出来的另一个集合,它有以下几个性质:
- 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
- 线性基是满足性质 1 的最小的集合。
- 线性基没有异或和为 0 的子集。
- 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
- 线性基中每个元素的二进制最高位互不相同。
因为满足了这样的性质,线性基可以构造出原数列可以得到的所有异或和。
构造
假设当前需要插入线性基的元素为 \(x\) ,那么具体构造方法就是:
从高到低扫描 \(x\) 的二进制位,如果当前位 \(i\) 为 \(1\),那么就尝试插入线性基,如果线性基的集合中已经有了最高位为 \(i\) 的元素,那么就将 \(x\) 异或上当前元素然后继续向低位扫描尝试插入;如果当前不含最高位为 \(i\) 的元素,那么就可以直接将 \(x\) 插入到线性基。参考代码可能更好理解(\(p[i]\) 表示二进制最高位为 \(i\) 的线性基元素):
void insert(int x)
{
for (int i=55;i>=0;i--)//从高到低扫描
if (x&((int)1<<i))//如果x当前位为1
if (!p[i]) {p[i]=x;break;}//如果没有元素,插入线性基
else x^=p[i];//否则异或上这个元素
}
如果不能理解的话可以自己手模一组数据,会发现将 \(x\) 异或 \(p[i]\) 这一操作正好就是做到了能够让原数列的多个数异或在一起这一情况,反正就是挺巧妙的。
查询最大异或和
查询最大异或和很简单,直接从高位到低位贪心即可,具体参照代码解释:
int queryMax()
{
int ans=0;
for (int i=55;i>=0;i--)
if ((ans^p[i])>ans) ans^=p[i];//如果ans异或上当前线性基可以得到更优答案,那么异或上p[i]即可
return ans;
}
因为 p[i] 的最高位会减小,而贪心会让高位能变成 \(1\) 就尽量变成 \(1\),所以正确性是可以保证的。
模板题(Luogu P3812)
【模板】线性基
题目背景
这是一道模板题。
题目描述
给定 \(n\) 个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
输入格式
第一行一个数 \(n\),表示元素个数
接下来一行 \(n\) 个数
输出格式
仅一行,表示答案。
样例 #1
样例输入 #1
2
1 1
样例输出 #1
1
提示
\(1 \leq n \leq 50, 0 \leq S_i < 2 ^ {50}\)
Solution
模板题,直接运用上面的操作即可。
需要注意 \(s\) 的取值范围,开 long long
。
Code
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
k=0;T flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
int n,a[55];
int p[70];
void insert(int x)
{
for (int i=55;i>=0;i--)
if (x&((int)1<<i))
if (!p[i]) {p[i]=x;break;}
else x^=p[i];
}
int queryMax()
{
int ans=0;
for (int i=55;i>=0;i--)
if ((ans^p[i])>ans) ans^=p[i];
return ans;
}
signed main()
{
read(n);
for (int i=1;i<=n;i++) read(a[i]),insert(a[i]);
printf("%lld\n",queryMax());
return 0;
}