线性基

线性基

什么是线性基

高级:对于一个数字集合,基于异或运算的最大线性无关组。
人话:给一个数组 a a a,线性基是另一个数组 b b b,且数组 b b b中的元素任意组合异或起来可以表示出数组 a a a中的任何元素组合异或。(数组 b b b元素很少,log级别)

有什么用

对于给出的数组,求出其中任意元素组合异或的最大值,最小值,k大值。

怎么搞

依次遍历数组 a a a中每一个元素 x x x,看看它能不能放进线性基里(初始时线性基为空)
放进线性基的条件就是说线性基里其他元素的自由组合异或起来不能表示出我这个数(如果能,那我这个 x x x就是多余的)
那我可以从 x x x的最高位向最低位遍历(这个过程是想看看线性基里的数字能不能组合出我)
若能组合出我,则满足 b 1 ⨁ b 2 . . . ⨁ b p = x b_1\bigoplus b_2...\bigoplus b_p=x b1b2...bp=x ⨁ \bigoplus 是异或运算, b b b是当前线性基),即为 x ⨁ b 1 ⨁ b 2 . . . ⨁ b p = 0 x\bigoplus b_1\bigoplus b_2...\bigoplus b_p=0 xb1b2...bp=0
那就要检查是否有一些 b b b异或我的 x x x等于0。
当遍历 x x x二进制各位数的时候(从高到低),若某一位是1,那就看看线性基里有没有同样这一位是1的,如果有就用这个数异或 x x x(将它变成0),否则,说明不可能存在一些 b b b把我的 x x x变成0,就把这个 x x x加到线性基里面。
这样,每个数最后不是被异或成0,就是被加入到线性基中。
这样构建出来的线性基,每一位都对应的至多有一个数字,所以至多大小只有log级别。这个线性基里的数字就可以各种组合出原数组中的组合(因为原数组中的每个数可以由线性基中组合而来,组合再组合就是可以表示出原数组的各种组合)。

线性基的并

即把线性基能表示出的数做合并操作。
直接并就可以。
把一个线性基插入到另一个线性基,插入规则和上面构建线性基是一样的(可以被组合出的话就不插入)。

线性基的交

即把线性基能表示出的数做交集(两个线性基都能表示的)。
这个比较玄学。
先说一下做法,假设我们要求 A A A线性基和 B B B线性基的交线性基。
首先构建一个线性基 C C C,一开始,这个线性基初值置为 A A A
按照从小到大的顺序依次遍历 B B B线性基,下面分两种情况(设当前遍历到元素 x x x)。
如果 x x x不可以被线性基 C C C表示,那么说明 x x x C C C中的元素们线性无关(线性无关即这些元素们不能互相表示),此时我把 x x x加到线性基 C C C里,由于线性无关,所以这样 C C C仍然是线性基。
如果 x x x可以被线性基 C C C表示,即 c 1 ⨁ c 2 . . . ⨁ c p = x c_1\bigoplus c_2...\bigoplus c_p=x c1c2...cp=x,当然这些数里既有 A A A中的元素也有 B B B中的元素,我们把这些数中 A A A贡献的部分加到答案线性基里。
然后就完事了。
正确性我也不晓得,如果有会的大佬希望能指点一哈。

如果你和我一样不知道它的原理,就直接粘贴下面的板子就可以了,包含了上面提到的所有东西(当然不是我写的,队友写的)

struct L_B {
	long long d[61], p[61];
	int cnt;
	L_B() {
		memset(d, 0, sizeof(d));
		memset(p, 0, sizeof(p));
		cnt = 0;
	}
	void init() {
		memset(d, 0, sizeof(d));
		memset(p, 0, sizeof(p));
		cnt = 0;
	}
 
	bool insert(long long val) {
		for(int i = 32; i >= 0; i--)
			if(val & (1LL << i)) {
				if(!d[i]) {
					d[i] = val;
					break;
				}
				val ^= d[i];
			}
		return val > 0;
	}
 
	long long query_max() {
		long long ret = 0;
		for(int i = 32; i >= 0; i--)
			if((ret ^ d[i]) > ret)
				ret ^= d[i];
		return ret;
	}
 
	long long query_min() {
		for(int i = 0; i <= 32; i++)
			if(d[i]) return d[i];
		return 0;
	}
 
	void rebuild() {
		for(int i = 32; i >= 0; i--)
			for(int j = i - 1; j >= 0; j--)
				if(d[i] & (1LL << j)) d[i] ^= d[j];
		for(int i = 0; i <= 32; i++)
			if(d[i]) p[cnt++] = d[i];
	}
 
	long long kthquery(long long k) {
		int ret = 0;
		if(k >= (1LL << cnt)) return -1;
		for(int i = 32; i >= 0; i--)
			if(k &(1LL << i)) ret ^= p[i];
		return ret;
	}
};

L_B Merge(L_B A, L_B B) {
	L_B All, C, D;
	for(int i = 32; i >= 0; i--) {
		All.d[i] = A.d[i];
		D.d[i] = 1ll << i;
	}
	for(int i = 32; i >= 0; i--) 
		if(B.d[i]) {
			ll v = B.d[i], k = 0;
			bool can = true;
			for(int j = 32; j >= 0; j--) 
				if(v & (1ll << j)) {
					if(All.d[j]) {
						v ^= All.d[j];
						k ^= D.d[j];
					}
					else {
						can = false;
						All.d[j] = v;
						D.d[j] = k;
						break;
					}
				}
 
			if(can) {
				ll v = 0;
				for(int j = 32; j >= 0; j--) 
					if(k & (1ll << j)) 
						v ^= A.d[j];
				C.insert(v);
			}
		}
	C.rebuild();
	return C;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值