线性基

线性基

定义

竞赛中一般用到的都是异或空间线性基。线性基可以看作针对一个数集 \(S\) 而产生的新集合,满足线性基中的任意数能产生的异或和的种类数和原集合能产生的异或和的种类数相同,且线性基的大小最小。

由此可以得出线性基中的几条性质(记线性基为 \(P\)):

  • 等价性。在原集合 \(S\) 上进行异或运算,和线性基 \(P\) 上进行异或运算的结果相同。

  • 最小性。即线性基的大小是极小的。

  • 线性基 \(P\) 中不存在异或和为 \(0\) 的子集。

    证明可以使用反证法:若存在 \(p_1\oplus p_2\oplus\cdots\oplus p_n\oplus p_x=0\),则 \(p_1\oplus p_2\oplus\cdots\oplus p_n=p_x\),则 \(p_x\) 是多余的,不满足最小性。

构造

可以使用贪心法构造线性基。假设我们已经构造好了 \(P\) 的一部分,现在要插入 \(x\)。按位从高到低枚举 \(P_i\),若第 \(i\) 位上 \(x\) 的值为 \(1\),就判断此时 \(P_i\) 的情况:若 \(P_i\) 没有值就插入,否则就将 \(x\) 异或 \(P_i\)

这样构造的正确性在于:不管插入的成功与否,我们都可以保证 \(x\) 被成功表示。如果插入成功,则插入前一路走来的 \(P_i\) 和插入的位置异或起来可以得到 \(x\);否则代表 \(x\) 本身就可以表示。

ll p[MAXN];
void ins(ll x){
	for(int i=50;~i;i--){
		if(!(x>>i)) continue;
		if(!p[i]) return p[i]=x,void();
		x^=p[i];
	}
}

不难发现单次插入的时间复杂度是 \(O(\log V)\),总空间复杂度也是 \(O(\log V)\)

应用

求异或最大值

不仅可以求一个数集 \(S\) 中任意数的异或最大值,还可以求任意数异或一个给定的 \(x\) 的异或最大值。具体而言,从高到低枚举二进制位,贪心地,尽量让当前位置是 \(1\) 即可。因为线性基第 \(i\) 位的数一定满足第 \(i\) 位是 \(1\) 且之前的位都是 \(0\),所以具体实现上直接让 \(\mathit{ans}\) 和异或 \(P_i\) 后的值取最大值即可。

ll sum(){
    ll res=0;
    for(int i=60;~i;i--) res=max(res,res^p[i]);
    return res;
}

求异或最小值

直接将上面的 \(\max\) 改为 \(\min\) 即可。

特殊地,如果是单纯求一个数集 \(S\) 中的异或最小值,可以直接输出线性基中的最小值。但是,如果这个数集中存在有数插入失败的情况,那么异或最小值就是 \(0\)

线性基合并

直接把一个线性基中的所有元素往另一个线性基中插一遍即可。复杂度 \(O(\log^2V)\)

实现的时候可以直接封装起来。

LB operator+(const LB&b){
    LB res=*this;
    for(int i=0;i<=60;i++){
        if(!b.p[i]) continue;
        res.ins(b.p[i],i);
    }
    return res;
}

求排名

对于去重的排名,考虑对于任意一个 \(P_i\),我们只关心它有没有值。如果有,说明 \(i\) 这一位可以选择是否异或来选择成为 \(0/1\),反之选择是固定的。所以我们从小到大枚举每一位,假设当前枚举到第 \(k\) 个有值的线性基,如果此时 \(x\) 同样有值,排名会加上 \(2^{k-1}\) 表示这一位为 \(0\) 的情形,反之不予操作即可。

int rnk(int x){
	int res=0,sm=1;
	for(int i=0;i<=30;i++){
		if(!p[i]) continue;
		if(x>>i&1) res=(res|sm)%MOD;
		sm<<=1;
	}
	return res;
}

对于不去重的排名,考虑在线性基中若有 \(k\) 个数插入失败,则这 \(k\) 个数都能任意组合选择和线性基内的一些数异或得到 \(0\)。那么任选方案有 \(2^k\) 种,用上面的 rnk 函数求出来的答案记为 \(s\),则最终答案是 \((s-1)\times2^k+1\)

带删除线性基

对于一些类似线段树分治一样的表述,不仅能用线段树分治解决,还能用带删除线性基解决。

仍然需要离线。首先需要知道每个时间点都干了些什么,如果是加了个数就需要预处理出这个数会在哪里被删掉。处理好这个之后,我们仍然按照时间处理操作,但是需要给插入线性基的数一个时间戳表示这个数被删除的时间,查询的时候只查询时间戳在当前时间之后的。对于线性基的每一位,时间戳应优先保留靠后的一个。

具体见代码:(BZOJ4184 shallot)

constexpr int MAXN=5e5+5;
int n,a[MAXN],del[MAXN];
unordered_map<int,int>mp;
struct{
	int p[32],t[32];
	void ins(int x,int tim){
		for(int i=30;~i;i--){
			if(!(x>>i)) continue;
			if(tim>t[i]) swap(x,p[i]),swap(tim,t[i]);
			if(!tim) return;
			x^=p[i];
		}
	}
	int ask(int tim){
		int res=0;
		for(int i=30;~i;i--) if(tim<t[i]) res=max(res,res^p[i]);
		return res;
	}
}LB;

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		if(a[i]>0) mp[a[i]]=i,del[i]=n+1;
		else del[mp[-a[i]]]=i;
	}
	for(int i=1;i<=n;i++){
		if(a[i]>0) LB.ins(a[i],del[i]);
		write(LB.ask(i));
	}
	return fw,0;
}
posted @ 2025-05-03 19:39  Laoshan_PLUS  阅读(121)  评论(0)    收藏  举报