线性基

最近进度推得飞快,还是要抽空总结的,不然左耳进右耳出?

$$\large 线性基$$

从定义开始,线性基是针对一个集合(表示该集合中的所有元素)构建的一个集合.
(一般这个线性基用于组合表示数字并进行相应统计,但是广义上说,它可以是一种矢量的组合,用来表示k维点集).
(而从数的二进制分解上来看,我们去异或得到对应线性基就相当于每个二进制位独立一维,而表示的点仅为有该维或没有,也是矢量).
这个东西有两种,一种实数线性基,一种异或线性基,应用较广泛的是后者,前者没学暂且不提,优先说下后者.
它能够将该集合中所有元素通过集合内部的元素组合(异或)来表示出来同时保证集合没有多余的元素.
这里注意两个概念,上面说了,这个线性基中没有多余的元素,我们称其满足线性无关,反之,无多余元素为线性无关.
在这样的前提定义下,不难看出所有原集合内部的所有数都能够通过线性基元素唯一组合得到,这是一个比较实用的性质.
那么我们说线性基是满足线性无关的一个集合,同时这个集合可以唯一表示我们要表示的目标集合(通过线性基元素间组合).
考虑什么时候这个元素多余,只有在其他元素可以通过元素间异或得到该元素时,这个元素是多余的.
那么如果我们需要构造出一个数集合的线性基,就用高斯消元或贪心.
后者要求排序从大向小,前者应用更加广泛.
考虑一个线性基,它一定满足它的每个元素在某一位是特异的,我们不妨按线性基的元素最高位分类.
因为每一个数位维度只有有(1)没有(0)这两种可能性,所以说这里的高斯消元通过异或可以直接将某个高位消去.
如果说当前遍历到的一个数,它的最高位之前没有出现过(之前没有该位),我们可以直接把它放入线性基内.
反之我们让它异或掉该位存储的数,再看该位会在哪里出现对应一个特异的数.
模拟一下这个过程就能发现它如果有效放入之后一定能够通过线性基内部的组合表示它本身.
而它不放入,说明原来的线性基就能够表示它,就不必放入.
所以剩下的就是代码了:

void check(int x){
	for(int i=31;i>=0;--i){//如果上界是longlong,该为63/62.因为没有负数,所以这里31/30不影响
		if((x>>i)&1){
			if(!bit[i]){//没有该特异位的元素,我们直接放入
				bit[i]=x;
				return ;
			}x^=bit[i];//反之接着构造.
		}
	}
}

例题:
albus就是要第一个出场
这个题想到用线性基之后,关键是想如何去统计它任何一位的答案.
分成两种数,一种放入了线性基内,一种没有.
考虑没有放入线性基内部的数,它一定能够通过线性基内部的数唯一组合得到.
那么也就是说,我们假设选出线性基内部的一个数组合,它能够对应到线性基外部的一个数,它们异或结果为0.
这时这几个数异或完,再任意异或一个数,该数不变,也就是说,我们可以任选线性基外部的所有元素组合,在线性基内找对应.
现在设线性基内有k个元素,原集合有n个元素.
那么所有的合法组合数为:\(2^{n-k}\)
再此基础上,我们考虑它输入进来的一个数,二进制分解它.
它的一位若在线性基中有数,记录有到它遍历位为止有几个线性基元素为\(s_i\),它总共有g位.
将方案数表示为:\(\sum^{g}_ {i=1}2^{s_i}×2^{n-k}\),这个题就解决了.

#include<bits/stdc++.h>
typedef long long ll;
#define qr qr()
#define ve vector
#define pa pair<int,int>
#define fi first
#define se second
using namespace std;
inline ll qr{
	ll x=0;bool f=0;char ch=getchar();
	while(ch>57||ch<48)f=(ch=='-')?1:0,ch=getchar();
	while(ch>=48&&ch<=57)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=2e5+200,mod=10086;
int n,num[N],bit[N],cnt=1;//cnt统计总共有多少不归于线性基的数.
void check(int x){
	for(int i=31;i>=0;--i){
		if((x>>i)&1){
			if(!bit[i]){
				bit[i]=x;
				// ++cnt;
				return ;
			}x^=bit[i];
			if(x==0){
				cnt=(cnt<<1)%mod;
				return ;
			}
		}
	}
}
void init(){
	n=qr;
	for(int i=1;i<=n;++i)
		num[i]=qr,
		check(num[i]);
	int q=qr,rk=0,s=1;
	for(int i=0;i<=30;++i){
		if(bit[i]){
			if((q>>i)&1)rk+=s;
			s<<=1;//前面有2^s种组合比它小
		}
	}rk%=mod;
	printf("%lld",(rk*cnt+1)%mod);
}
int main(){
	




	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	



	init();
	return 0;
}
posted @ 2024-07-23 11:39  SLS-wwppcc  阅读(14)  评论(0)    收藏  举报