前缀线性基

当多次询问,每次都需要用到区间的线性基的时候,有一个朴素的想法,即拆分区间。
用线段树或倍增数组维护小区间的线性基,当询问任意区间时,将若干小区间合并即可。
但线性基合并的复杂度为\(O(log^2V)\),若每次询问再拆分成\(O(logn)\)个小区间,那总复杂度为\(O(log^2Vlogn)\)
能否降低复杂度呢,考虑获取区间信息有一种简单的方式,即仅维护前缀和信息,通过两段前缀和作差得到区间信息。
但是这样做的前提是维护信息需满足区间可减性,线性基貌似看上去并不满足,事实上并不满足,但是仍可以通过类似的方式去维护,称之为前缀线性基。
\(CF1100F\)
https://www.luogu.com.cn/problem/CF1100F
题意:给定一个长\(n\)的序列\(a_i\),多次询问,每次询问给定\(l,r\),回答从\([l,r]\)中选若干数异或,能得到的最大值。
题解:设\(len\)为线性基内存储向量的长度,即\(len=logV\)
先考虑序列的某个前缀\([1,i]\)的所有后缀的线性基\([j,i]\)是相互包含的,即\([j,i]\)的线性基总是包含\([j+1,i]\)的线性基。
这就是说一定能通过向空集不断添加新向量,得到\([i,i]\)\([1,i]\)这些后缀的线性基,那么这些后缀的线性基中互不相同的至多只有\(O(len)\)种。
因此对于每个添加的向量\(v\),都标记他出现的最大下标\(t\),就可以在\(O(n)\)的空间内存储所有后缀的线性基。
查询\([j,i]\)的线性基,只需要在\(i\)处的前缀线性基中,仅保留\(t\geq j\)的向量即可。
显然两作用相同的向量,要尽可能的保留\(t\)更大的,才能够覆盖尽可能多的区间。
具体来说,前缀线性基构造流程如下:
\(1\):为线性基中保留的每个向量\(a_x\)都保存一个时间戳\(t_x\),初始全为\(0\)
\(2\):添加序列第\(i\)个向量\(v\)时,仍从高位向低位扫,同时记录当前时间\(i\)
\(3\):设当前扫到第\(x\)位,若\(v\)的第\(x\)位为\(1\),若线性基中第\(x\)位无向量,则直接插入。
若线性基中第\(x\)位已有向量,则比较两向量,若\(i>t_x\),即要添加的向量更晚,更优,则替换\(v\)\(a_x\)\(i\)\(t_x\)

#include <bits/stdc++.h>

using namespace std;

const int N=5e5+10;

struct LinearBasis{
	int a[20],t[20];
	void insert(int x,int y){
		for(int i=19; i>=0; i--)
			if(x>>i&1){
				if(!a[i]){
					a[i]=x,t[i]=y;
					break;
				}
				if(y>t[i]){
					swap(a[i],x);
					swap(t[i],y);
				}
				x^=a[i];
			}
	}
	int query(int k){
		int res=0;
		for(int i=19; i>=0; i--)
			if(t[i]>=k) res=max(res,res^a[i]);
		return res;
	}
}b[N];

int main(){
	int n;
	cin >> n;
	for(int i=1; i<=n; i++){
		int x;
		cin >> x;
		memcpy(b[i].a,b[i-1].a,sizeof b[i-1].a);
		memcpy(b[i].t,b[i-1].t,sizeof b[i-1].t);
		b[i].insert(x,i);
	}
	int m;
	cin >> m;
	for(int i=1; i<=m; i++){
		int l,r;
		cin >> l >> r;
		cout << b[r].query(l) << endl;
	}
	return 0;
}

\(CF1902F\)
https://www.luogu.com.cn/problem/CF1902F
题意:给定一棵\(n\)个点的树,每个点有点权,多次询问\(x,y,k\),问能否从\(x\)\(y\)的简单路径上选若干点,使其异或和为\(k\)
题解:只需快速得到\(x\)\(y\)简单路径上点集构成的线性基,则能快速回答询问。
考虑用\(x\rightarrow p\)\(p\rightarrow y\)两线性基合并,即得到\(x\rightarrow y\)的线性基。
而考虑对每个点存储该点到根路径上点集的线性基,则所需线性基即为\(x,y\)到根路径上的一段后缀构成的线性基。
将每个向量的额外属性,时间\(t\)改为深度\(d\),每次找\(x\)对应线性基中\(d\geq d_p\)的向量即可。

#include <bits/stdc++.h>

using namespace std;

const int N=2e5+10;

int n,m;
int a[N],dep[N],f[N][22];
int h[N],e[2*N],ne[2*N],idx;

struct LinearBasis{
	int a[20]={},d[20]={};
	void insert(int x,int y){
		for(int i=19; i>=0; i--)
			if(x>>i&1){
				if(!a[i]){
					a[i]=x,d[i]=y;
					break;
				}
				if(y>d[i]){
					swap(a[i],x);
					swap(d[i],y);
				}
				x^=a[i];
			}
	}
	int query(int x,int k){
		int res=x;
		for(int i=19; i>=0; i--)
			if(d[i]>=k) res=min(res,res^a[i]);
		return res;
	}
}b[N];

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int depth){
	dep[u]=depth;
	memcpy(b[u].a,b[f[u][0]].a,sizeof b[f[u][0]].a);
	memcpy(b[u].d,b[f[u][0]].d,sizeof b[f[u][0]].d);
	b[u].insert(a[u],dep[u]);
	for(int i=h[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==f[u][0]) continue;
		f[j][0]=u;
		for(int k=1; k<=20; k++)
			f[j][k]=f[f[j][k-1]][k-1];
		dfs(j,depth+1);
	}
}

int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20; i>=0; i--)
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=20; i>=0; i--)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}

int main(){
	memset(h,-1,sizeof h);
	cin >> n;
	for(int i=1; i<=n; i++) cin >> a[i];
	for(int i=1; i<=n-1; i++){
		int a,b;
		cin >> a >> b;
		add(a,b),add(b,a);
	}
	dfs(1,1);
	cin >> m;
	while(m--){
		int x,y,k;
		cin >> x >> y >> k;
		LinearBasis t;
		for(int i=20; i>=0; i--)
			if(b[x].a[i]) t.insert(b[x].a[i],b[x].d[i]);
		for(int i=20; i>=0; i--)
			if(b[y].a[i]) t.insert(b[y].a[i],b[y].d[i]);
		if(!t.query(k,dep[lca(x,y)])) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}
posted @ 2025-05-30 18:12  junliang123  阅读(235)  评论(0)    收藏  举报