线性基学习笔记

一、概念

线性基实际上就是维护了一个数组 \(p\),满足 \(p_i\) 在二进制下的最高位为第 \(i\) 位。

二、实现

现在我们有一个数组 \(a\),我们要构造他的线性基 \(p\)

每次插入 \(a_i\) 时,我们都从高位往低位遍历,用以寻找第一个空位插入它。当然,我们也不能直接把原数插入。为了方便查询是否存在该数,每次到达 \(a_i\) 最高位但是 \(p_j\) 已经有值时,\(a_i\gets a_i\oplus p_j\)

时间复杂度 \(O(n\log V)\)

void add(int x){
	for(int i=52;~i;i--)
		if(x&(1ll<<i)){
			if(p[i]) x^=p[i];
			else return p[i]=x,void();
		}
}

三、应用

  1. 查询当前插入的数值是否能够异或出 \(x\)
    实际上我们可以理解为插入 \(x\),能插得进去就说明不能,否则就是可以。
int check(int x){
	for(int i=52;~i;i--){
		if((x&(1ll<<i))==0) continue;
		if(p[i]) x^=p[i];
		else return 0;
	}return 1;
}
  1. 查询当前数能凑出的异或最大值 \(/\) 最小值。
    最小值就是从小往大枚举第一个 \(>0\)\(p_i\),假如在之前发现能凑出 \(0\),那就是 \(0\)。判断是否能凑出 \(0\) 很简单,直接在插入前判断是否能够凑出要插入的数就可以。
    最大值从大往小枚举,假如异或 \(p_i\)\(ans\) 更大,那么 \(ans\gets ans\oplus p_i\)
int maxn(){
	int ans=0;
	for(int i=52;~i;i--)
		ans=max(ans,ans^p[i]);
	return ans;
}int minn(){
	if(fl) return 0;
	for(int i=0;i<53;i++)
		if(p[i]) return p[i];
}
  1. 查询一个值在线性基中的排名
    我在 \([luogu4869]\ albus\) 就是要第一个出场 中遇到了这个问题。当然,这个问题还要和下文的特殊性质 \(1\) 结合。
    我的方法是在查询中暴力跑出当前选小的后的值,假如要比 \(x\) 小,就去右子树,否则就去左子树。时间复杂度 \(O(\log^2V)\)
    网上好像有单词查询 \(O(\log V)\) 预处理 \(O(\log^2V)\) 的做法,懒得学了。
//luogu4869 albus就是要第一个出场
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=35,p=10086;
int n,ps[N],d[N],q,id,sum;
int qpow(int x,int y){
	int re=1;
	while(y){
		if(y&1) re=re*x%p;
		x=x*x%p,y>>=1;
	}return re;
}void add(int x){
	for(int i=30;~i;i--)
		if(x&(1ll<<i)){
			if(!ps[i]){
				for(int j=30;j>i;j--) d[j]++;
				return ps[i]=x,sum++,void();
			}x^=ps[i];
		}
}int ans(int x,int nw,int y){
	if(nw<0) return (y==x?1:0);
	if(!ps[nw]) return ans(x,nw-1,y);
	int z=min(y,y^ps[nw]),now=nw-1;
	while(~now) z=max(z,z^ps[now--]);
	if(z>=x) return ans(x,nw-1,min(y,y^ps[nw]));
	return (ans(x,nw-1,max(y,y^ps[nw]))+qpow(2,d[nw])*id)%p;
}signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1,a;i<=n;i++)
		cin>>a,add(a);
	id=qpow(2,n-sum),cin>>q;
	cout<<ans(q,30,0);
	return 0;
}

当然,线性基也可以辅助其他算法或思想(如贪心)使用。

四、特殊性质

  1. 长度为 \(n\) 的数组 \(V\),其线性基为 \(B\),定义 \(c_v=\bigoplus\limits_{a\in v}a\)\(num_k=\sum\limits_{v\subseteq V}[c_v=k]\),则 \(\forall k\in\mathbb{N},num_k\in\{0,2^{n-|B|}\}\)

五、经典套路

  1. 带删除离线线性基
    这个玩意可以用线段树分治离线维护。
//BZOJ4184 Shallot
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=35;
int n,ps[M],vis[N];
unordered_map<int,int>pr;
vector<int>g[N*4];
void add(int x){
	for(int i=30;~i;i--)
		if(x&(1<<i)){
			if(ps[i]) x^=ps[i];
			else return ps[i]=x,void();
		}
}int maxn(){
	int ans=0;
	for(int i=30;~i;i--)
		ans=max(ans,ans^ps[i]);
	return ans;
}void chg(int x,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R)
		return g[x].push_back(v),void();
	int mid=(l+r)/2;
	if(L<=mid) chg(x*2,l,mid,L,R,v);
	if(R>mid) chg(x*2+1,mid+1,r,L,R,v);
}void solve(int x,int l,int r){
	int nps[M];
	for(int i=0;i<31;i++)
		nps[i]=ps[i];
	for(auto y:g[x]) add(y);
	int mid=(l+r)/2;
	if(l==r) cout<<maxn()<<"\n";
	else solve(x*2,l,mid),solve(x*2+1,mid+1,r);
	for(int i=0;i<31;i++)
		ps[i]=nps[i];
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>vis[i];
		if(vis[i]<0){
			chg(1,1,n,pr[-vis[i]],i-1,-vis[i]);
			vis[pr[-vis[i]]]=0,vis[i]=0;
		}else pr[vis[i]]=i;
	}for(int i=1;i<=n;i++)
		if(vis[i]) chg(1,1,n,i,n,vis[i]);
	solve(1,1,n);
	return 0;
}
  1. 树上可以走重边的路径异或和 \(=\) 一条路径的异或和 \(\oplus\) 多个环的异或和。
    利用该思想,可以将路径和环的异或和扔到线性基中。对于这个线性基来说,本质不同的路径长度就是所有路径长度在线性基中能取到的最小值的种类数。
//BZOJ2322 梦想封印
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20005,M=65;
struct edge{int to,w;};
int n,m,q,ps[M],ans[N],d[N],nm,cc;
int u[N],v[N],w[N],xr[N],vs[N];
vector<edge>g[N];set<int>st;
int add(int x){
	for(int i=62;~i;i--){
		if(!((1ll<<i)&x)) continue;
		if(!ps[i]){
			ps[i]=x,nm++;
			return 1;
		}x^=ps[i];
	}return 0;
}int lst(int x){
	for(int i=62;~i;i--)
		x=min(x,x^ps[i]);
	return x;
}void work(){
	st.clear();
	for(int i=1;i<=n;i++) 
		st.insert(lst(xr[i]));
}void dfs(int x,int fa){
	vs[x]=1,cc++;
	for(auto ed:g[x]){
		int y=ed.to,c=ed.w;
		if(y==fa) continue;
		if(!vs[y]){
			xr[y]=xr[x]^c;
			st.insert(lst(xr[y]));
			dfs(y,x);continue;
		}int cr=xr[x]^xr[y]^c;
		if(add(cr)) work();
	}
}signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>q,st.insert(0);
	for(int i=1;i<=m;i++)
		cin>>u[i]>>v[i]>>w[i];
	for(int i=1;i<=q;i++)
		cin>>d[i],ans[d[i]]=1;
	for(int i=1;i<=m;i++){
		if(!ans[i]){
			g[u[i]].push_back({v[i],w[i]});
			g[v[i]].push_back({u[i],w[i]});
		}else ans[i]=0;
	}dfs(1,0),ans[q]=(1ll<<nm)*st.size()-1;
	for(int i=q-1;~i;i--){
		int x=u[d[i+1]];
		int y=v[d[i+1]];
		int c=w[d[i+1]];
		g[x].push_back({y,c});
		g[y].push_back({x,c});
		if(vs[x]&&vs[y]){
			int cr=xr[x]^xr[y]^c;
			if(add(cr)) work();
		}else if(vs[x]^vs[y]){
			if(!vs[x]) swap(x,y);
			st.insert(lst(xr[x]^c));
			xr[y]=xr[x]^c,dfs(y,x);
		}ans[i]=(1ll<<nm)*st.size()-1;
	}for(int i=0;i<=q;i++)
		cout<<ans[i]<<"\n";
	return 0;
}
  1. 线性基合并
    直接暴力把一个线性基的东西 \(add\) 到另一个里就行了,没什么好说的。
int ps[65],b[65];
void merge(){
	for(int i=0;i<=62;i++) add(b[i]);
}//把 b 合并到 a 里
posted @   长安一片月_22  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示