异或线性基

线性基

线性空间下的一组基

对于线性空间 \(V\) ,有一组线性无关子集 \(S\) ,能张成 \(V\) ,称 \(S\) 是 \(V\) 的基,一般考虑有限空间下的,则 \(S\) 的大小就是 \(V\) 的维数。

异或线性基的构造

考虑贪心

对于插入数 \(p\) ,如果 \(p\) 第 \(x\) 位为 \(1\)

当\(a_x\) 为空,则 \(a_x=p\)

否则 \(p=p \bigoplus a_x\)

\(code:\)

inline bool insert(int x){
	for(int i=T;i>=0;i--)
		if((x>>i)&1)
			if(b[i]) x^=b[i];
			else{
				b[i]=x;
				return true;
			}
	return 0;		
}

最大异或和

考虑贪心

从 \(T\) ~ \(1\) 位遍历线性基 ,如果 \(ans \bigoplus a_i>ans\) ,就更新 \(ans\)

\(code:\)

inline int gmax(int v){
	int ans=v;
	for(int i=T;i>=0;i--)
		if((ans^b[i])>ans) ans^=b[i];
	return ans;
}

最小异或和

\(insert(0)\) 失败,就说明插入的数已经可以得到了,最小异或和为 \(0\)

否则最小异或和就等于线性基中的最小元素

代码就不给了qwq

求并集

其实不难,把线性基中的元素插入到另外一个线性基中就行了

inline friend F operator +(F x,F y){
	F ret=y;
	for(int i=T;i>=0;i--){
		if(x[i]){
			ret.insert(x[i]);
		}
	}
	return ret;
}

求交集

用的很少,就是把线性基中的元素插入到另外一个线性基中会失败的元素

封装

挺清新的(((

template<int T> struct F{
	int b[T+5];
	F(){
		for(int i=0;i<=T;i++) b[i]=0;
	}
	inline bool insert(int x){
		for(int i=T;i>=0;i--)
			if((x>>i)&1)
				if(b[i]) x^=b[i];
				else{
					b[i]=x;
					return true;
				}
		return 0;		
	}
	inline int gmax(int v){
		int ans=v;
		for(int i=T;i>=0;i--)
			if((ans^b[i])>ans) ans^=b[i];
		return ans;
	}
	inline int operator [](int x){
		return b[x];
	}
	inline friend F operator +(F x,F y){
		F ret=y;
		for(int i=T;i>=0;i--){
			if(x[i]){
				ret.insert(x[i]);
			}
		}
		return ret;
	}
	inline void clear(){
		for(int i=T;i>=0;i--) b[i]=0;
	}
};

例题

P3812 【模板】线性基

链接

模板题

复杂度

\(O(n\times \log V)\)

P4151 [WC2011] 最大XOR和路径

链接

题意:
求从 \(1\) ~ \(n\) 经过所有边边权异或和最大的路径

Solution

路径可以分成环和链,我们可以初始答案为\(1\) ~ \(n\) 的一条路径,不断增加环,遇到一个环我们把环上的边权异或和求出来为 \(c\) ,令当前异或和为 \(sum\)

如果选这个环,则 \(sum=sum \bigoplus w \bigoplus c \bigoplus w =sum \bigoplus c\)

我们把环全放到线性基里

最后求最大异或和即可。

\(Q:\) \(1\) 到 \(n\) 的路径有很多条怎么办?

\(A:\) 如果对于我们选的路径 \(R\) ,有更优的路径 \(S\) ,你会发现 \(R\) 和 \(S\) 起点都是 \(1\) 终点都是 \(n\) ,已经构成一个环了,\(R\) 异或大环的异或和就等于 \(S\) 的异或和,与选择的路径无关。

复杂度

\(O((m+n) \times \log V)\)

\(code:\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=65,MAXM=200020,B=63;
long long n,m,lb[MAXN+10],ans[MAXM];
inline bool insert(long long x){
	for(int i=B;i>=0;i--){
		if((x>>i)&1){
			if(!lb[i]){
				lb[i]=x;
				return true;
			}
			x^=lb[i];
		}
	}
	return false;
}
inline long long sum(long long x){
	long long ans=x;
	for(int i=B;i>=0;i--) if((ans^lb[i])>ans) ans^=lb[i];
	return ans;
}
struct node{ int r; long long w; };
vector<node> p[MAXM];
bitset<MAXM+10> vis,cle;
inline void dfs(int k,long long sum){
	ans[k]=sum,vis[k]=1;
	for(int i=0;i<p[k].size();i++)
		if(!vis[p[k][i].r]) dfs(p[k][i].r,sum^p[k][i].w);
		else insert(sum^p[k][i].w^ans[p[k][i].r]);
}
int main(){
	scanf("%lld%lld",&n,&m);
	long long x,y,z;
	for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&x,&y,&z),p[x].push_back({y,z}),p[y].push_back({x,z});
	dfs(1,0);
	printf("%lld\n",sum(ans[n]));
	return 0;
}

CF895C Square Subsets

链接

题意:对于一些数组a,求从中间选择非空子集,使它们的乘积等于某个整数的平方的方法的数量

\(n \leq 10^5 ,a_i \leq 70\)

Solution:

由于 \(a_i\) 很小,可以对每个质数构造一维线性基

质因数分解 \(a_i=\prod p_i^{c_i}\)

命 \(b_i=\sum_{i=1}^{pcnt} (c_i \mod 2) \times 2^i\)

将 \(b_i\) 插入线性基,可以发现当 \(b_i\) 的异或和等于 \(0\) 就有一组解

不在线性基的 \(n-size\) 个数随便选,所以最后答案是 \(2^{n-size}\)

复杂度 \(O(n \times \frac{V}{\log V})\)

#include<bits/stdc++.h>
using namespace std;
int n,cnt,size,x[100010],p[75];
long long mod=1000000007;
bool vis[100];
bitset<30> a[30];
inline void insert(int u){
	bitset<30> vx;
	for(int i=1;i<=cnt&&u!=1;i++){
		int cntk=0;
		while(u%p[i]==0) u/=p[i],cntk++;
		vx[i]=cntk%2;
	}
	for(int i=cnt;i>=1;i--){
		if(vx[i]){
			if(!a[i].any()){
				a[i]=vx;
				size++;
				return;
			}
			vx^=a[i];
		}
	}
}
long long f(long long a,long long b){
	if(a==0) return 1;
	if(a==1) return b;
	long long w=f(a/2,b);
	if(a%2) return w*w%mod*b%mod;
	return w*w%mod;
}
int main(){
	for(int i=2;i<=75;i++){
		if(!vis[i]) p[++cnt]=i;
		for(int j=1;j<=cnt;j++){
			if(p[j]*i>75) break;
			vis[p[j]*i]=1;
		}
	}
	cin>>n;
	for(int i=1;i<=n;i++) cin>>x[i];
	for(int i=1;i<=n;i++){
		insert(x[i]);
	}
	printf("%lld\n",(f(n-size,2)-1ll+mod)%mod);
	return 0;
}

P7451 [THUSCH2017] 杜老师

题目大意

给定 \(L,R\) 求从 \(L\) 到 \(R\) 的这 \(R-L+1\) 个数中能选出多少个不同的子集,满足子集中所有的数的乘积是一个完全平方数。特别地,空集也算一种选法,定义其乘积为 \(1\)。

思路

因为乘积是个完全平方数,所以自然可以想到乘积的每个质因子的次数为偶数,我们可以把每个质因子的次数看为一维的线性基,最后所有数异或的值为 \(0\) 的情况数,所以我们可以作一个质因子的线性基,最后的答案就是 \(2^{r-l+1-size}\) ,但是很明显,如果把 \(1\) ~ \(10000000\) 的所有质数都作线性基肯定会炸。

如果一个数有一个大于 \(\sqrt n\) 的质因子,那么它一定不会有第二个大于 \(\sqrt n\) 的质因子,所以我们只要把一个数小于 \(\sqrt n\) 的质因子筛去,单独做大于 \(\sqrt n\) 的质因子的线性基,最后复杂度是 \(354 \times (r-l) ≈ 10^{10}\) ,还是无法通过。

发现只要 \(l\) 到 \(r\) 有一个数有 \(p\) 这个质因子,那么一定可以构成 \(p\) 的线性基,对于判断 \(l\) 到 \(r\) 之间是否有 \(p\) 这个因子,只要有 \((\frac{r}{p} )>(\frac{l-1}{p})\) ,那么一定有 \(p\) 这个因子存在,暴力一遍搜每个质因子就行了。

code:


#include<bits/stdc++.h>
using namespace std;
int T;
const long long MAXN=10000000,MAXM=1000000,B=455,MXB=7000,mod=998244353;
long long l,r,cnt1,cnt2,size,p1[MAXM+10],p2[MAXM+10];
bitset<MAXN+10> vis,vis2,cle2;
bitset<470> a[470],cle;
map<int,bitset<470>> mp;
inline void insert(long long x){
	bitset<470> vx;
	for(int i=1;i<=cnt1&&x!=1;i++){
		int cnt=0;
		while(x%p1[i]==0) cnt++,x/=p1[i];
		vx[i]=cnt%2;
	}
	if(x!=1){
		if(!mp[x].any()&&!vis2[x]){
			mp[x]=vx,size++,vis2[x]=1;
			return;
		}
		vx^=mp[x];
	}
	for(int i=B;i>=1;i--){
		if(vx[i]){
			if(!a[i].any()){
				a[i]=vx;
				size++;
				return;
			}
			vx^=a[i];
		}
	}
}
long long f(long long a,long long b){
	if(b==0) return 1;
	if(b==1) return a;
	long long w=f(a,b/2);
	if(b%2) return w%mod*w%mod*a%mod;
	return w*w%mod;
}
inline void init(){
	for(long long i=2;i<=MAXN;i++){
		if(!vis[i]){
			if(i<=3200) p1[++cnt1]=i;
			p2[++cnt2]=i;
			if(i*i<=MAXN) for(long long j=i*i;j<=MAXN;j+=i) vis[j]=1;
		}
	}
}
int main(){
	scanf("%d",&T);
	init();
	while(T--){
		scanf("%lld%lld",&l,&r);
		map<int,bitset<470>>().swap(mp),size=0,vis2&=cle2;
		for(int i=0;i<=465;i++) a[i]&=cle;
		if(r-l+1<=MXB) for(long long i=l;i<=r;i++) insert(i);
		else for(long long i=1;i<=cnt2&&p2[i]<=r;i++) if(r/p2[i]>(l-1)/p2[i]) size++;
		printf("%lld\n",f(2,r-l+1-size));
	}
	return 0;
} 

P4839 P 哥的桶

链接

单点insert,区间最大异或和

考虑线段树,每个节点维护区间线性基,求答案时求并集的最大异或和,复杂度 \(O(m \log n \log V^2)\)

\(code:\)

#include<bits/stdc++.h>
#define ll long long
#define SF scanf
#define PF printf
#define PB push_back
#define cmax(x,y) x=max(x,y);
#define cmin(x,y) x=min(x,y);
#define ull unsigned long long
#define R register
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define IOS ios::sync_with_stdio(false)
using namespace std;
template<int T> struct F{
	int b[T+5];
	F(){
		for(int i=0;i<=T;i++) b[i]=0;
	}
	inline bool insert(int x){
		for(int i=T;i>=0;i--)
			if((x>>i)&1)
				if(b[i]) x^=b[i];
				else{
					b[i]=x;
					return true;
				}
		return 0;		
	}
	inline int gmax(int v){
		int ans=v;
		for(int i=T;i>=0;i--)
			if((ans^b[i])>ans) ans^=b[i];
		return ans;
	}
	int operator [](int x){
		return b[x];
	}
	friend F operator +(F x,F y){
		F ret=y;
		for(int i=T;i>=0;i--){
			if(x[i]){
				ret.insert(x[i]);
			}
		}
		return ret;
	}
	inline void clear(){
		for(int i=T;i>=0;i--) b[i]=0;
	}
};
template<int T>struct segtree{
	struct node{
		int l,r;
		F<31> v;
		int add;
		node(){	l=r=add=0;} 
	}t[T*4+5]; 
	#define l(i) (i<<1)
	#define r(i) ((i<<1)|1)
	int a[T+5];
	segtree(){ for(int i=1;i<=T;i++) a[i]=0; }
	void build(int l,int r,int id){
		t[id].l=l,t[id].r=r;
		if(l==r){
			return;
		}
		int mid=(l+r)>>1;
		build(l,mid,l(id));
		build(mid+1,r,r(id));
	}
	void add(int x,int y,int id){
		if(t[id].l==t[id].r){
			t[id].v.insert(y);
			return;
		}
		int mid=(t[id].l+t[id].r)>>1;
		if(x<=mid) add(x,y,l(id));
		else add(x,y,r(id));
		t[id].v=t[l(id)].v+t[r(id)].v;
	}
	F<31> ask(int l,int r,int id){
		F<31> ret;
		if(t[id].l>r||t[id].r<l)  return ret;
		if(t[id].l>=l&&t[id].r<=r) return t[id].v;
		return ask(l,r,l(id))+ask(l,r,r(id));
	}
};
segtree<50010> t;
int n,m;
int main(){
	IOS;
	cin>>n>>m;
	t.build(1,m,1);
	while(n--){
		int opt,x,y;
		cin>>opt>>x>>y;
		if(opt==1) t.add(x,y,1);
		else printf("%d\n",t.ask(x,y,1).gmax(0));
	}
	return 0;
} 

P5607 [Ynoi2013] 无力回天 NOI2017

链接

题意:
区间异或,区间最大异或和

区间异或维护原序列很难,发现修改其实只对少部分线段树节点的线性基有影响

类似于差分,用线段树维护数组 \(b\),其中 $b_i=a_{i-1} \bigoplus a_i $ , \(b_1=a_1\)

维护 \(b_i\) 的线性基,在查询 \(l\) ,\(r\) 时,取出区间 \(l+1,r\) 的线性基,并 \(insert\) 一个 \(a_l\)

最后的线性基与 \(a_l\) ~ \(a_r\) 是相同的

特判 \(l==r\) 即可

对于区间修改 \(b\) 数组,就只用改 \(b_l\) 和 \(b_{r+1}\)

线段树修改叶子结点直接重构就行

维护 \(a_l\) 可以用线段树或树状数组维护

复杂度 \(O(m \times \log n \times \log V^2)\)

\(code:\)

#include<bits/stdc++.h>
#define ll long long
#define SF scanf
#define PF printf
#define PB push_back
#define cmax(x,y) x=max(x,y);
#define cmin(x,y) x=min(x,y);
#define ull unsigned long long
#define R register
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define IOS ios::sync_with_stdio(false)
using namespace std;
template<int T> struct F{
	int b[T+5];
	F(){
		for(int i=0;i<=T;i++) b[i]=0;
	}
	inline bool insert(int x){
		for(int i=T;i>=0;i--)
			if((x>>i)&1)
				if(b[i]) x^=b[i];
				else{
					b[i]=x;
					return true;
				}
		return 0;		
	}
	inline int gmax(int v){
		int ans=v;
//	for(int i=T;i>=0;i--) if(b[i]) cout<<i<<' '<<b[i]<<endl;
		for(int i=T;i>=0;i--)
			if((ans^b[i])>ans) ans^=b[i];
		return ans;
	}
	inline int operator [](int x){
		return b[x];
	}
	inline friend F operator +(F x,F y){
		F ret=y;
		for(int i=T;i>=0;i--){
			if(x[i]){
				ret.insert(x[i]);
			}
		}
		return ret;
	}
	inline void clear(){
		for(int i=T;i>=0;i--) b[i]=0;
	}
};
template<int T>struct BIT{
	int c[T+5];
	inline int lowbit(int x){
		return x&(-x);
	}
	inline void xg(int x,int y){
		for(int i=x;i<=T;i+=lowbit(i)) c[i]^=y;
	}
	inline void add(int l,int r,int w){
		xg(l,w);
		xg(r+1,w);
	}
	inline int get(int x){
		int ans=0;
		for(;x>=1;x-=lowbit(x)) ans^=c[x];
		return ans;
	}
};
BIT<50050> f;
template<int T>struct segtree{
	struct node{
		int l,r;
		F<31> v;
		node(){	l=r=0;} 
	}t[T*4+5]; 
	#define l(i) (i<<1)
	#define r(i) ((i<<1)|1)
	int a[T+5];
	segtree(){ for(int i=1;i<=T;i++) a[i]=0; }
	void build(int l,int r,int id){
		t[id].l=l,t[id].r=r;
		for(int i=l;i<=r;i++) t[id].v.insert(a[i]);
		if(l==r) return;
		int mid=(l+r)>>1;
		build(l,mid,l(id));
		build(mid+1,r,r(id));
	}
	F<31> ask(int l,int r,int id){
		F<31> ret;
		if(t[id].l>r||t[id].r<l)  return ret;
		if(t[id].l>=l&&t[id].r<=r) return t[id].v;
		return ask(l,r,l(id))+ask(l,r,r(id));
	}
	void add(int x,int w,int id){
		if(t[id].l==t[id].r){
			t[id].v.clear();
			t[id].v.insert(a[x]^=w);
			return;
		}
		int mid=(t[id].l+t[id].r)>>1;
		if(x>mid) add(x,w,r(id));
		else add(x,w,l(id));
		t[id].v=t[l(id)].v+t[r(id)].v;
	}
};
segtree<50010> t;
int n,m;
inline int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch-'0'),ch=getchar();
	return x; 
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) t.a[i]=read();
	for(int i=n;i>=2;i--) t.a[i]^=t.a[i-1];
	for(int i=1;i<=n;i++) f.xg(i,t.a[i]);
	t.build(1,n,1);
	while(m--){
		register int opt,x,y,v;
		opt=read(),x=read(),y=read(),v=read(); 
		if(opt==1) {
			f.add(x,y,v),t.add(x,v,1);
			if(y<n) t.add(y+1,v,1);
		}
		else {
			F<31> ret=t.ask(x+1,y,1);
			int al=f.get(x),ans;
			ret.insert(al);
			if(x==y) ans=max(v,v^al);
			else ans=ret.gmax(v);
			printf("%d\n",ans);
		}
//		for(int i=1;i<=n;i++) printf("a[%d]:%d\n",i,f.get(i));
	}
	return 0;
} 

CF1100F Ivan and Burgers

链接

求区间最大异或和

\(n \leq 5 \times 10^5\)

\(Solution 1:\)

直接线段树暴力合并, \(3\) 只 \(\log\)

\(Solution 2:\)

贪心,让线性基中后面维度的下标尽量大,求解就取出 \(1\) ~ \(r\) 的线性基

我们记录线性基第 \(i\) 位为 \(a_i\) ,下标为 \(pos_i\)

如果 \(pos_i<l\) 则 \(l\) ~ \(r\) 不存在第 \(i\) 位线性基

inline int gmax(int v,int l){
	int ans=v;
	for(int i=T;i>=0;i--)
		if((ans^b[i])>ans&&pos[i]>=l) ans^=b[i];
	return ans;
}

那怎么让 \(pos_i\) 尽可能小呢

假设我们正插入 \(p\) ,下标为 \(x\)

当 \(p\) 第 \(i\) 位为 \(1\) 且已经有 \(a_i\)

如果 \(x>pos_i\) 我们 \(swap(x,pos_i)\) , $ swap(p,a_i)$

\(swap\) 后再将\(p =p\bigoplus a_i\)

防止存太多线性基 \(MLE\) ,可以离线询问 \(r\) 从小到大扫描 ,空间变为线性

时间复杂度 $ O((n+m) \times \log V) $

口糊不太清楚,看代码更清晰-->

\(code:\)

#include<bits/stdc++.h>
#define ll long long
#define SF scanf
#define PF printf
#define PB push_back
#define cmax(x,y) x=max(x,y);
#define cmin(x,y) x=min(x,y);
#define ull unsigned long long
#define R register
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define IOS ios::sync_with_stdio(false)
using namespace std;
template<int T> struct F{
	int b[T+2],pos[T+2];
	F(){
		for(int i=0;i<=T;i++) b[i]=pos[i]=0;
	}
	inline bool insert(int x,int y){
		for(int i=T;i>=0;i--)
			if((x>>i)&1){
				if(b[i]){
					if(pos[i]<y) swap(b[i],x),swap(y,pos[i]);
					x^=b[i];
					continue;
				}
				b[i]=x;
				pos[i]=y;
				return true;
			}
		return 0;		
	}
	inline int gmax(int v,int l){
		int ans=v;
		for(int i=T;i>=0;i--)
			if((ans^b[i])>ans&&pos[i]>=l) ans^=b[i];
		return ans;
	}
	int operator [](int x){
		return b[x];
	}
};
int n,m,a[500005],ans[500010];
F<21> p;
struct ask{
	int l,r,id;
}c[500050];
inline bool cmp(ask a,ask b){
	return a.r<b.r;
}
int main(){
	cin>>m;
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>c[i].l>>c[i].r,c[i].id=i;
	sort(c+1,c+1+n,cmp);
	int r=0;
	for(int i=1;i<=n;i++){
		while(r<c[i].r) p.insert(a[++r],r);
		ans[c[i].id]=p.gmax(0,c[i].l);
	}
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
} 

\[\Huge END \]

异或线性基
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线性可以用来判断原集合是否封闭。如果一个元素能够被线性向量线性表示,那么它就可以由原集合中的元素经过线性组合得到,即原集合是封闭的。否则,如果有一个元素不能被线性向量线性表示,那么它就无法由原集合中的元素经过线性组合得到,即原集合不是封闭的。 具体地,我们可以通过将待判断的元素与线性向量进行异或操作来判断是否能够线性表示。如果待判断元素与线性向量进行异或操作后得到零向量,则说明待判断元素可以由线性向量线性表示。如果待判断元素与线性向量进行异或操作后得到非零向量,则说明待判断元素无法由线性向量线性表示。 因此,我们可以通过判断待判断元素与线性向量进行异或操作的结果是否为零向量来判断原集合是否封闭。如果待判断元素与线性向量进行异或操作后都得到零向量,则原集合是封闭的;否则,原集合不是封闭的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [线性模板](https://blog.csdn.net/weixin_43519854/article/details/96977900)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【矩阵论】线性空间线性变换(3)(4)](https://blog.csdn.net/kodoshinichi/article/details/108916238)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值