线性基专题

知识预备

线性基详解
线性基学习笔记

线性基的定义

由原集合 A A A得到线性基 T T T
使得 T T T中元素互相异或所形成的集合,等价于原序列 A A A的元素互相异或形成的集合
可以理解为线性基将原序列进行了压缩

线性基的性质

  1. 线性基能相互异或得到原集合的所有相互异或得到的值
  2. 线性基是满足性质 1 1 1的最小的集合
  3. 线性基没有异或和为 0 0 0的子集

线性基的构造

设一个数组 d d d,表示序列 a a a的线性基,下标从 0 0 0开始
d[i]表示的是线性基中第 i + 1 i+1 i+1个位置上所存储的数字
如果这个数字不为 0 0 0
那么这个数字转化为二进制数后的第 i + 1 i+1 i+1位是 1 1 1
且这个二进制数的最高位是第 i + 1 i+1 i+1
我们都将序列 a a a中每一个数依次插入线性基中,得到了序列 a a a的线性基
下面是向线性基中插入一个数的代码

bool insert(ll val){//向线性基中插入一个数
	for(ll i=59;i>=0;i--){
		if(val&1ll<<i){
			if(d[i])val^=d[i];
			else{
				d[i]=val;
				break;
			}
		}
	}
	return val>0;//判断val是否插入成功
}

我们可以看到,在线性基中插入数 v a l val val时,从高位到低位依次扫描它为 1 1 1的二进制位
当扫描到第i位时,如果 d [ i ] d[i] d[i]不存在,就令 d [ i ] = v a l d[i]=val d[i]=val,插入结束
如果d[i]存在,此时 v a l val val d [ i ] d[i] d[i]的第 i + 1 i+1 i+1位都为 1 1 1,就令 v a l val val= v a l val val^ d [ i ] d[i] d[i]
于是 v a l val val d [ i ] d[i] d[i]异或后第 i + 1 i+1 i+1位变成了 0 0 0,然后继续向下扫描
最终, v a l val val会有插入成功和插入不成功这两种结局
如果插入成功,就说明当前线性基里的一些数异或起来不能等于 v a l val val,因此 v a l val val是不可替代的
如果插入不成功,是因为当前线性基里的一些数异或起来可以等于 v a l val val
并且 v a l val val在一系列操作之后变成了 0 0 0,因此 v a l val val是多余的

查询最大异或和

准确地说,是求一个序列中的若干个数的异或和的最大值
从高位到低位扫描线性基,如果答案异或 d [ i ] d[i] d[i]后变大了,就让答案异或 d [ i ] d[i] d[i]
这其实是个贪心的过程,我们只需让答案的高位尽可能大
当扫到 d [ i ] d[i] d[i] d [ i ] d[i] d[i]不为 0 0 0
由于 d [ i ] d[i] d[i]的第 i + 1 i+1 i+1位是 1 1 1,且d[i]的 i + 1 i+1 i+1位以上都是 0 0 0
所以如果答案的第 i + 1 i+1 i+1位是 0 0 0,则答案异或上 d [ i ] d[i] d[i]之后一定会变大
如果答案的第 i + 1 i+1 i+1位是 1 1 1,则答案异或上 d [ i ] d[i] d[i]之后一定会变小

ll query_max(){
	ll ans=0;
	for(ll i=59;i>=0;i--)
		if((ans^d[i])>ans)
			ans^=d[i];
	return ans;
}

查询最小异或和

最小值其实就是最小的 d [ i ] d[i] d[i]
这是因为如果让最小的 d [ i ] d[i] d[i]去异或其它的 d [ i ] d[i] d[i],那么它一定会变大,所以它自己就是最小的
显然,如果这个线性基有无法插入的数,那么最小异或和就为 0 0 0

查询第k小异或和

准确地说:从一个序列中取任意个元素进行异或,求能异或出的所有数字中第 k k k小的值。
要求第 k k k小值,首先将线性基进行改造,改造后每一个 d [ i ] d[i] d[i]相互独立
对于每一个 d [ i ] d[i] d[i],枚举 j j j= i i i to 1 1 1,如果 d [ i ] d[i] d[i]的第 j j j位为1,那么让 d [ i ] d[i] d[i]异或 d [ j − 1 ] d[j−1] d[j1]
这样改造后,如果 d [ i ] d[i] d[i]不为 0 0 0,那么所有 d [ j ] d[j] d[j]的第 i + 1 i+1 i+1位上为 1 1 1的只有 d [ i ] d[i] d[i]
于是线性基中的元素,作用其实都是提供自己最高位上的 1 1 1
那么只要使提供出来的 1 1 1可以和 k k k的每一位上的 1 1 1对应
那么求出来的答案就是第 k k k小的

void rebuild(){
	for(int i=59;i>=0;i--)
		for(int j=i-1;j>=0;j--)
			if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
	rebuild();
	int cnt=0;
	for(int i=0;i<=59;i++)if(d[i])cnt++;
	if(cnt<n&&k==1)return 0;//最小异或和为0的情况
	if(cnt<n)k--;//最小异或和为0的情况
	if(k>=(1ll<<cnt))return -1;//不存在第k小异或和
	ll ans=0;
	for(int i=0;i<=59;i++)if(d[i]){
		if(k&1)ans^=d[i];
		k>>=1;
	}
	return ans;
}

例一:HDOJ 3949

题意

给定n(n≤10000) 个数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an​​,以及 Q(Q≤10000)个询问,每次询问这些数(至少一个,不能不选)能够组成的异或和中第 k k k小的数是什么(去掉重复的异或和)。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
const int M=1e4+5;
int cs,n,m,cnt;
ll k,A[M],d[60];
void insert(ll x){
	for(int i=59;i>=0;i--){
		if(x&1ll<<i){
			if(d[i])x^=d[i];
			else{
				d[i]=x;
				break;
			}
		}
	}
}
void rebuild(){
	for(int i=59;i>=0;i--)
		for(int j=i-1;j>=0;j--)
			if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
	if(cnt<n&&k==1)return 0;
	if(cnt<n)k--;
	if(k>=(1ll<<cnt))return -1;
	ll ans=0;
	for(int i=0;i<=59;i++)if(d[i]){
		if(k&1)ans^=d[i];
		k>>=1;
	}
	return ans;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
//	freopen("jiedai.out","w",stdout);
#endif
	rd(cs);
	for(int cas=1;cas<=cs;cas++){
		printf("Case #%d:\n",cas);
		memset(d,0,sizeof(d));
		rd(n);
		for(int i=1;i<=n;i++)rd(A[i]),insert(A[i]);
		rd(m);
		rebuild();
		cnt=0;
		for(int i=0;i<=59;i++)if(d[i])cnt++;
		while(m--)rd(k),printf("%lld\n",query_kth(k));
	}
	return (0-0);
}

例二:HDOJ 6579

题意

有n个数,m次操作,强制在线。
操作0 l r:询问[l,r]的最大异或和;
操作1 x:序列的最后添加一个数x。

分析

暴力的做法可以用数据结构维护区间线性基,但肯定过不了。贪心地维护序列的前缀线性基 (上三角形态),对于每个线性基,将出现位置靠右的数 字尽可能地放在高位,也就是说在插入新数字的时候,要同时记录对应位置上数字的出现位 置,并且在找到可以插入的位置的时候,如果新数字比位置上原来的数字更靠右,就将该位 置上原来的数字向低位推。 在求最大值的时候,从高位向低位遍历,如果该位上的数字出现在询问中区间左端点的 右侧且可以使答案变大,就异或到答案里。 对于线性基的每一位,与它异或过的线性基更高位置上的数字肯定都出现在它右侧 (否 则它就会被插入在那个位置了),因此做法的正确性显然。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
const int M=1e6+5,K=31;
int n,m,cas,A[M];
int d[M][K],id[M][K];
void build(int val,int pos){
	int tmp=pos;
	for(int i=29;i>=0;i--){
		d[pos][i]=d[pos-1][i];
		id[pos][i]=id[pos-1][i];
	}
	for(int i=29;i>=0;i--){
		if(val&1<<i){
			if(d[pos][i]){
				if(tmp>id[pos][i]){
					swap(d[pos][i],val);
					swap(id[pos][i],tmp);
				}
				val^=d[pos][i];
			}
			else{
				d[pos][i]=val;
				id[pos][i]=tmp;
				break;
			}
		}
	}
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
//	freopen("jiedai.out","w",stdout);
#endif
	rd(cas);
	while(cas--){
		rd(n),rd(m);
		for(int i=1;i<=n;i++)rd(A[i]),build(A[i],i);
		int ans=0,op,x,l,r;
		while(m--){
			rd(op);
			if(op)rd(A[++n]),build(A[n]^=ans,n);
			else{
				rd(l),rd(r);
				l=(l^ans)%n+1;
				r=(r^ans)%n+1;
				if(l>r)swap(l,r);
				ans=0;
				for(int i=29;i>=0;i--)if(id[r][i]>=l)MAX(ans,ans^d[r][i]);
				printf("%d\n",ans);
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=0;j<30;j++)
				d[i][j]=id[i][j]=0;
	}
	return (0-0);
}

例三:xor [线性基求交]

链接

https://ac.nowcoder.com/acm/contest/884/B

题意

给定n个集合,m次询问,对于询问 ( L , R , x ) (L,R,x) (L,R,x),询问处于 [ L , R ] [L,R] [L,R]中的集合是否都能从各自的集合中选出若干个数使得其异或和为 x x x

分析

显然这是一个线段树+线性基求交。
要得到线性基A与B的交集,构造一个线性基C,首先,C的初始值为A。
遍历B线性基B,当遍历到元素x时:
1.若元素x不能被线性基C表示,说明x与线性基C中的元素线性无关(即这些元素们之间不能互相表示),那么就将元素x插入到线性基C中。
2.若元素x能被线性基C表示,即线性基C中的若干个元素的异或和为x,那么这些元素中必定有线性基A中的元素,也可能有线性基B中的元素,然后将那些只属于线性基A的元素加入到答案线性基中。(或者只将线性基B中的元素插入也行)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
const int M=5e4+5;
int n,m;
ll tr[M<<2][32];
void insert(ll *d,ll x){
	for(int i=31;i>=0;i--){
		if(x&1ll<<i){
			if(d[i])x^=d[i];
			else {d[i]=x;break;}
		}
	}
}
void merge(ll *res,ll *A,ll *B){
	static ll C[32],D[32],tmp[32];
	for(int i=0;i<32;i++){
		C[i]=A[i];
		D[i]=1ll<<i;
		tmp[i]=0;
	}
	for(int i=0;i<32;i++){
		if(B[i]){
			ll x=B[i],k=0,flag=1;
			for(int j=31;j>=0;j--){
				if(x&1ll<<j){
					if(C[j]){
						x^=C[j];
						k^=D[j];
					}
					else{
						C[j]=x;
						D[j]=k;
						flag=0;
						break;
					}
				}
			}
			if(flag){
				ll x=0;
				for(int j=0;j<32;j++)if(k&1ll<<j)x^=C[j];
				insert(tmp,x);
			}
		}
	}
	for(int i=0;i<32;i++)res[i]=tmp[i];
}
void build(int l=1,int r=n,int p=1){
	if(l==r){
		ll cnt,x;
		rd(cnt);
		while(cnt--)rd(x),insert(tr[p],x);
		return;
	}
	int mid=l+r>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	merge(tr[p],tr[p<<1],tr[p<<1|1]);
}
bool query(int a,int b,ll x,int l=1,int r=n,int p=1){
	if(l>b||r<a)return true;
	if(l>=a&&r<=b){
		for(int i=31;i>=0;i--){
			if(x&1ll<<i){
				if(tr[p][i])x^=tr[p][i];
				else return false;
			}
		}
		return true;
	}
	int mid=l+r>>1;
	return query(a,b,x,l,mid,p<<1)&query(a,b,x,mid+1,r,p<<1|1);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
//	freopen("jiedai.out","w",stdout);
#endif
	rd(n),rd(m);
	build();
	while(m--){
		int l,r,x;
		rd(l),rd(r),rd(x);
		puts(query(l,r,x)?"YES":"NO");
	}
	return (0-0);
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值