加载中...

线性基

很久之前就学过线性基了,但由于蒟蒻实力很差,很难碰到这类题。但线性基又是 acm 的一个高频考点。故蒟蒻决定写这篇博客来彻底搞懂线性基。

学线性基之前,先了解下线性基可以解决哪些问题:

pV8RDlq.png

线性相关与线性无关:对于 \(\forall 向量a_{1},a_{2},...,a_{n}\),若存在一组 \(\{k_{1},k_{2},...,k_{n}\}\) 使得 \(\sum_{i=1}^{n} k_{i}a_{i} = 0\),则称向量 \(a_{1},a_{2},...,a_{n}\) 线性相关,否则线性无关。

  • 线性基定义:
    pV84jVf.png

(!!注意)异或空间线性基 只能表示 原集合的子集的非0异或空间 \(\Rightarrow\) 异或线性基并不能反映原集合能否异或出0 \(\Rightarrow\) 需要单独标记

  • 线性基的性质:
    pV8fuRA.png

构造异或线性基的两种方法:

  • 高斯消元法
// 高斯消元法 构造线性基
int n;
int k; // k表示当前构造线性基的个数
// 注意高斯消元法涉及到a内元素位置的交换,最终构造出的线性基数组就是a
ll a[55]; // index0

void solve()
{
	cin >> n;
	for(int i = 0; i < n; i ++){
		cin >> a[i];
	}

	for(int c = 50; c >= 0; c --){
		for(int i = k; i < n; i ++){
			if(a[i] >> c & 1){
				swap(a[i], a[k]);
				break;
			}
		}
		if(!(a[k] >> c & 1)) continue;
		for(int i = 0; i < n; i ++){
			if(i != k && (a[i] >> c & 1)){
				a[i] ^= a[k];
			}
		}
		k ++; if(k == n) break;
	}
}

pV8fci4.png

  • 贪心法
// 贪心法 构造线性基
int n;
ll a[55]; 
ll basis[55]; // 与高斯消元不同,可以单开一个数组记录线性基

void insert(ll x){
	for(int c = 50; c >= 0; c --){
		if(x >> c & 1){
			if(basis[c]){ // 若该位的线性基已经存在
				x ^= basis[c]; // 则消除掉x中该位的1,以保护basis[c]中对应位的1
			}
			else{ // 否则直接将该数插入到线性基中
				basis[c] = x;
				break; 
			}
		}
	}
}

void solve()
{
	memset(basis, 0, sizeof basis);
	cin >> n;
	for(int i = 0; i < n; i ++){
		cin >> a[i];
		insert(a[i]); // 依次插入
	}
}

pV8fRzR.png

两种构造方法的区别之处:

pV8ffQ1.png

  • 求一个集合的所有子集的异或和第k小:
// 利用线性基求某集合所有子集的第k小异或和:
// 首先要明确,原集合中所有子集的异或集合 与其 异或线性基中的所有子集的异或集合 是一样的
// 注意:必须用高斯消元法,因为用高斯消元法求得的线性基中的每个元素在其对应最高位上可以保证有唯一的1
// 这样可以发现选取线性基中的子集构造出的元素单调性 和 线性基中编号 呈现一种二进制上的单调性(细细品味)
// 因此,求第k小 就可以直接用 k的二进制表示得到线性基子集编号 对应的子集异或值
// 同时要注意,这种技巧得到的第k小只是非0的第k小(选取线性基中的任意非空子集,异或和一定不为0)
// 而实际上,原数组中的某个子集异或后的结果可能是0,0一定是最小的
// 因此还需要考虑原数组的子集是否还可以异或出来0:
// 直接看线性基大小是否等于原集合大小,若等于,则不能;否则能。(?未懂,待补)

int n;
int k; 
ll a[10010]; 
int q;

// HDU 3943
void solve()
{ 
	k = 0; // 多测,需要初始化线性基指针
	cin >> n;
	for(int i = 0; i < n; i ++){
		cin >> a[i];
	}

	// 高斯消元法求线性基
	for(int c = 61; c >= 0; c --){
		for(int i = k; i < n; i ++){
			if(a[i] >> c & 1){
				swap(a[i], a[k]);
				break;
			}
		}
		if(!(a[k] >> c & 1)) continue;
		for(int i = 0; i < n; i ++){
			if(i != k && (a[i] >> c & 1)){
				a[i] ^= a[k];
			}
		}
		k ++; if(k == n) break;
	}

	cin >> q;
	while(q --){
		ll x; cin >> x; // 求异或和第x小
		if(k < n){ // 原集合可以异或出0
			x --;
		}
		if(x >= (1ll << k)){ // 线性基最多只能异或出(2^k - 1)个非0值
			cout << -1 << endl;
			continue; 
		}
		ll ans = 0;
		for(int i = k - 1; i >= 0; i --){
			if(x >> i & 1){
				ans ^= a[k - i - 1];
			}
		}
		cout << ans << endl;
	}
}
  • 求一个集合的所有子集的异或和方案数:

pV84T8H.png

例题:
P4570 code
P4301 code

posted @ 2025-07-22 01:33  jxs123  阅读(133)  评论(0)    收藏  举报