浅谈算法——线性基

线性基是竞赛中常用来解决子集异或一类题目的算法。(摘自百度百科)


线性基是一个整数序列的特殊集合,每个序列都至少存在一个线性基,同一个序列可以拥有多个不同的线性基;反过来,一个线性基只对应一个确定的序列。

线性基存在如下三条性质

  • 原序列中任意一个数都可以由若干个线性基内的数异或得到
  • 线性基内的数无法异或得到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;
}
posted @ 2020-09-17 22:34  Wolfycz  阅读(479)  评论(0编辑  收藏  举报