浅谈算法——线性基
线性基是竞赛中常用来解决子集异或一类题目的算法。(摘自百度百科)
线性基是一个整数序列的特殊集合,每个序列都至少存在一个线性基,同一个序列可以拥有多个不同的线性基;反过来,一个线性基只对应一个确定的序列。
线性基存在如下三条性质:
- 原序列中任意一个数都可以由若干个线性基内的数异或得到
- 线性基内的数无法异或得到0
- 线性基内没有重复元素,在保证性质一的前提下,线性基内的元素个数是最少的
在证明性质之前,首先看一下线性基的一种可行的构造法,基于这种构造法,能够保证线性基的三条性质
const int N=50;
ll A[N+10];
void Add(ll x){
for (int i=N;~i;i--){
if (x&(1ll<<i)){
if (A[i]) x^=A[i];
else{
A[i]=x;
break;
}
}
}
}
根据其构造方法,我们可以得出\(A\)数组的一个性质,若\(A[i]\)不为0,则\(A[i]_{(2)}\)的第\(i+1\)位必定为1,且\(A[i]_{(2)}\)的最高位即为第\(i+1\)位(证明略)
然后,我们来对其性质进行证明
性质一
对于一个数\(x\),它仅存在两种结果,即成功插入线性基或者失败。分情况进行讨论:
如果\(x\)不能插入线性基,那么\(x\)在最后必然变成了0,则有\(x\otimes A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=0\)(\(\otimes\)表示异或),那么则有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=x\),满足性质一
如果\(x\)插入了线性基,记\(x\)插入的位置为\(i\),则有\(x\otimes A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=A[K_i]\),那么则有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]\otimes A[K_i]=x\),同样满足性质一
性质二
其实蛮显然的……如果存在\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_p]=0\),那么就有\(A[K_1]\otimes A[K_2]\otimes...\otimes A[K_{p-1}]=A[K_p]\),与构造方法矛盾,故假设不成立,性质二证毕
性质三
根据构造方法可得,线性基内不可能存在重复元素;我们也可以知道,线性基内元素增加,当且仅当序列中的新元素\(x\)无法被原线性基中的元素表达,除此之外,线性基内不会增加新的元素,那么性质三证毕
然后,关于线性基的一些简单应用
求最大值
具体而言,求一个序列中,若干个数异或和的最大值
首先构造这个序列的线性基,由于\(A\)数组的性质——即\(A[i]_{(2)}\)的最高位必然是1,我们只需要从最高位开始贪心取数即可
for (int i=N;~i;i--) if ((Ans^A[i])>Ans) Ans^=A[i];
求最小值
如果是一个序列异或和最小值,判断是否存在元素不能插入线性基,如果有,则答案为0,否则,答案与线性基内异或和最小值相同,即为最小的\(A[i]\)
求第K小的值
具体而言,求一个序列,若干个数异或和的第K小值
首先对线性基进行处理,对于一个\(A[i]_{(2)}\),若其第\(j\)位为1,则\(A[i]=A[i]\otimes A[j-1]\),这样处理后,线性基依旧对应同一个序列,即处理前与处理后的线性基为同一序列的不同线性基,证明略
处理完后,线性基内的元素大致如下(已转成二进制,x表示0或1)
1x0xx0x0
1xx0x0
1x0
1
若\(K_{(2)}\)的第\(x\)位为1,则\(Ans=Ans\otimes A[B_x]\),记\(A\)数组中非零元素个数为\(n\),则\(B\)数组满足\(A[B_1]>A[B_2]>...>A[B_n]>0\)
ll k_th(int k){
ll Ans=0;
for (int i=0;i<=N;i++){
if (A[i]){
if (k&1) Ans^=A[i];
k>>=1;
}
}
}
特殊情况请自行处理(如K=0或K=1等)
证明?在更改完线性基后,\(A[B_x]\)的作用为提供其最高位的1,故按\(K_{(2)}\)中1的位置,来对应相应的\(A[B_x]\),答案即为第K小的(具体过程略)
判断一个数能否被线性基中的数异或得到
构造函数添加返回值即可
删除
咕咕咕,之后填坑
模板题:P3812 【模板】线性基
Description
给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
Input
第一行一个数n,表示元素个数
接下来一行n个数
Output
仅一行,表示答案。
Sample Input
2
1 1
Sample Output
1
HINT
\(1\leqslant n\leqslant 50,0\leqslant S_i\leqslant 2^{50}\)
/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline char gc(){
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>inline T frd(T x){
int f=1; char ch=gc();
for (;ch<'0'||ch>'9';ch=gc()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=gc()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
template<typename T>inline T read(T x){
int f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x<0) putchar('-'),x=-x;
if (x>9) print(x/10);
putchar(x%10+'0');
}
template<typename T>inline T min(T x,T y){return x<y?x:y;}
template<typename T>inline T max(T x,T y){return x>y?x:y;}
template<typename T>inline T Swap(T &x,T &y){T t=x; x=y,y=t;}
const int N=50;
ll A[N+10];
void Add(ll x){
for (int i=N;~i;i--){
if (x&(1ll<<i)){
if (A[i]) x^=A[i];
else{
A[i]=x;
break;
}
}
}
}
int main(){
int n=read(0); ll Ans=0;
for (int i=1;i<=n;i++) Add(read(0ll));
for (int i=N;~i;i--) if ((Ans^A[i])>Ans) Ans^=A[i];
printf("%lld\n",Ans);
return 0;
}