异或和与线性基问题入门

概述

在OI/ACM中,线性基一般特指布尔域上的基。由于布尔域和二进制关系密切,同时有限域只有有限状态,所以容易被出成题。

线性基用于解决子集异或和有关的问题。例如要求查询“任选几个数,使其异或和最大”。如果题目要求并非“任选”而是有所限制,一般无法用线性基解决,因为求基后无法满足限制条件。

基本操作

线性基的性质

求“任选几个数异或和的所有可能值”,实际上就是把这些数视为二进制向量,求它们张成的线性空间。
根据线性代数知识,可以对这些向量进行初等行变换,而不影响结果。因此我们把它化为行阶梯型矩阵,或行最简形矩阵。
这样,就消除了多余的变量,得到一组基向量。如果化为行最简型,那么这样的基是唯一的。

观察这个阶梯型或最简型,只有每行行首所在的列01可以任意变动。所有可能异或和的个数,自然就是 \(2^{\mathbb{dim}\ \mathbb{linspan}(vec)} \),即2的矩阵秩次幂。
所以任意输入一个数(二进制向量),只需要用这些关键列确定它应该是由哪几个基组成,以及判断它是否合法。

这样行阶梯型求出来的基具有很好的性质,可直接从高位到低位贪心得到最大值,实际上也是线性拟阵的一个例子。
如果要求最小值,则显然是最小的那个基向量。

线性基常见模板

如下代码就表示了维护行阶梯型,并求最大值的代码。我们仍使用数字表示一个布尔向量,于是unsigned long long范围内,最多有64个基。
用base[i]表示首位位置在i上的基向量,否则为0。则base本身是个上三角矩阵,base中的非零值就形成一个阶梯型矩阵,这种表示相当方便。
有时最简形更实用,比如用于求解kth。可以直接在加入过程中维护最简形,需要先把base[i]本身可以清零的位异或掉,再把大于base[i]的基第i列异或掉 。也可以再需要时再把阶梯型化为最简形。
求kth时,只需把关键列调整位二进制的k即可。

  1. //we may need <bitset> sometimes
  2. typedef unsigned long long ll;
  3. ll base[64];
  4. void add(ll x){
  5. 	for (int i=63;i>=0;i--)
  6. 		if (x&1ull<<i)
  7. 			if (!base[i]){
  8. 				base[i]=x;
  9. 				return;
  10. 			}
  11. 			else x^=base[i];
  12. }
  13.  
  14. //test if x can perform by xor sum
  15. bool test(ll x){
  16. 	for (int i=63;i>=0;i--)
  17. 		if (x&1ull<<i)
  18. 			if (!base[i]) return 0;
  19. 			else x^=base[i];
  20. 	return 1;
  21. }
  22.  
  23. //max xor sum
  24. ll maxc(){
  25. 	ll ans=0;
  26. 	for (int i=63;i>=0;i--)
  27. 		if ((ans^base[j])>ans)
  28. 			ans^=base[i];
  29. 	return ans;
  30. }
  31.  
  32. //min xor sum
  33. ll minc(){for (int i=0;i<64;i++) if (base[i]) return base[i];}
  34.  
  35. //query kth max number
  36. //k should not larger than 2^(dim linspan(x))
  37. ll kth(ll k){
  38. 	ll ans=0,tmp[64],res=0,cnt=0;
  39.     for(T i=0;i<64;i++){ //set matrix to simplest form
  40.         for(int j=i-1;j>=0;j--)
  41. 			if(base[i]&1ull<<j) base[i]^=base[j];
  42.         if(base[i])tmp[cnt++]=base[i];
  43.     }
  44. //!!error here
  45. 	for (int i=63;i>=0;i--)
  46. 		if (k&1ull<<i)
  47. 			ans^=base[i];
  48. 	return ans;
  49. }

加强版

加长

如果布尔向量很长,上述代码可改用bitset。但要注意,和高斯消元一样,时间复杂度达到O(n^3),空间复杂度O(n^2),只是常数很小。

线性基合并,区间和问题

两个线性空间W1,W2,它们的集合并W1∪W2不一定是个线性空间,也就不存在线性基。我们这里说的线性基合并,是指线性空间的:{W1+W2}。

合并两个线性基只能一个个插入O(n^3),没有复杂度更优的做法。然而平时的一些题目中,数据范围不会很大,例如32位整数或64位整数。这时即使数据量很大,写出来是个又瘦又高的矩阵,它的线性基也只能是那么多个。

如果我们把单次合并视为常数时间,那大概是32*32这样的大常数。

有了这个思想,看看这个问题:(2017西安区域赛A)询问区间内某个子集异或和再异或上k的最大可能值。
前面说过,线性基无法回答某个子集异或和这种非整体性的问题,所以这道题中,我们求出的是所有询问区间的线性基。既然线性空间的并运算是可交换可结合的,那么暴力预处理出线段树,在线段树上合并区间即可。
时间复杂度:O(nlog(n)*d^2)+O(Qlog(n)*d^2),d为数据位数。
空间复杂度:O(n*d)

线性空间交

两个线性空间交必然是线性子空间。想要求出交空间的线性基,有以下结论:

设{α}是V1的基,{β}是V2的基,W={β}∩V1。若 {α}∪({β}-W) 线性无关,则W是交空间的基。

我们想要求出W,方法是先尝试合并为线性基tmp,即在{α}的基础上依此插入{β}的元素。同时,记录线性基tmp中由{α}贡献的部分。若尝试插入βi的是两个空间的交叠部分,必然与tmp产生线性相关无法插入。注意到此时βi是由V1和V2合并得到的基向量线性表出;而根据定义,βi可以只用{α}线性表出,所以把它由tmp中{α}贡献的部分加入答案。(把{β}贡献的部分加入答案也可)

  1. Basis Ingersect(Basis &A, Basis &B){
  2.     Basis tmp=A,v1=A,v2=B, ans;
  3.     for(LL i=0;i<32;i++){
  4.         if(v2.basis[i]){
  5.             bool flag=true;
  6.             LL x=v2.basis[i],now=0;
  7.             for(LL j=31;j>=0;j--)
  8.                 if(x&(1ll<<j)){
  9.                     if(!tmp.basis[j]){
  10.                         flag=false; //can't be represent
  11.                         tmp.basis[j]=x; //so insert
  12.                         v1.basis[j]=now;
  13.                         break;
  14.                     }
  15.                     x^=tmp.basis[j]; //same as Basis.insert
  16.                     now^=v1.basis[j];
  17.                 }
  18.             if(flag) ans.insert(now);
  19.         }
  20.     }
  21.     return ans;
  22. }

构建全部子空间

虽然所有相同维数的线性子空间是同构的,具有完全相同的代数性质;或者说,忽视列之间的区别进行初等列变换,得到等价标准型是相同的。但有时,题目中仍要求区分不同的列,所以不能进行列变换。

既然一个矩阵化为行最简形矩是唯一的,那么一个行最简形确定了一个线性子空间。
我们不妨猜测一下这样的子空间有多少个,例如\(\mathbb{Z}_2^5\)(五维布尔向量)下有多少个不同的线性空间?
这样的空间显然不超过C(32,5)个,因为最多5个基向量,每个向量32种选择;进一步地,它不应该超过2^15个,因为行阶梯型要求base数组是5阶上三角01矩阵;实际情况因为行最简形限制会更少;另一方面,它至少大于2^5个,因为不同的列子集一定生成不同的空间。
实际情况384个,我也不知道可不可以算出来。

下面是搜索 \(\mathbb{Z}_2^5\) 所有子空间的代码,出自cf1299d。本题和该代码的巧妙之处在于,由于只有32种向量,我们完全可以用一个uint32表示出整个线性子空间,而不必只存储它的基。搜索空间和合并空间也十分简单。

  1. map<ui,int> id; ui state[N]; int ST;
  2.  
  3. //search all linear space of Z_2^5
  4. void dfs_state(ui v){
  5. 	if (id.count(v)) return;
  6. 	id[v]=++ST; state[ST]=v;
  7. 	for(int i=1;i<32;i++) if (!(v>>i&1)){ //try add vector i
  8. 		ui w=v;
  9. 		for (int j=0;j<32;j++) //add element 
  10. 			if(v>>j&1) w|=1u<<(i^j); 
  11. 		dfs_state(w);
  12. 	}
  13. }
  14. ui meg(ui a, ui b){
  15. 	//if (!a||!b) return 0; //bad space, already has co-linear
  16. 	if ((a>>1)&(b>>1)) return 0; //no intersection
  17. 	ui ret=0; //direct sum
  18. 	for (int i=0;i<32;i++)
  19. 		for (int j=0;j<32;j++)
  20. 			if ((a>>i&1) && (b>>j&1)) ret|=1u<<(i^j);
  21. 	return ret;
  22. }

不是线性基的异或和问题

区间和

有些问题虽然也是异或和,但和线性基无关。例如:
给定一列数字,求异或和最大的区间。

这里要求的是一个区间而不是子集,所以它可能是个数据结构问题,或者说至少和线性基没有半毛钱关系。

要解决上面的问题,首先看一个简单的问题:给一些数,然后每次询问X,求哪个数与X的异或值最大。

显然从高到低位贪心。为了方便查询,只用把给定的数建成0-1 trie。

那么求最大区间的问题也解决了。考虑到xor是其自身的逆运算,设前缀和为S,区间[l,r]异或和即S[l-1] xor S[r]。于是枚举一端查询即可。

区间和的加强

加强版:1、每次询问l,r,x,询问区间内异或x的最大值。 2、除了询问异或和最大的区间,还有在末尾插入新的数。

这两个问题一样,要用到可持久化Trie。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注