线性基

分析

用于解决异或问题,可以表示异或的所有值

删除

在线

  • 如果在线性基外,直接删

  • 如果在线性基内

    • 如果可以有代替的数,删掉代替的
    • 如果没有,找到最小的被他异或过的数(存在传递关系),将其依次异或被他异或过的数(消除影响)

    时间复杂度\(O(n+lgn)Q\)

离线

线段树分治

求交并

求并

暴力插入,时间复杂度\(O(nlg^2n)\)

求交

https://blog.csdn.net/djyanglinhan/article/details/88825831

例题1

ybtoj E. 有趣的三元组

显然对于每组\(u,v\),将环做成线性基,然后查询线性基中的能表达的数(只算一次)的和

考虑如何做后者

枚举每一位,如果能被在线性基中,则出现次数为\(2^{num-1}\),若不在,则出现次数为\(2^{num}\),其中必须出现在\(u,v\)路径上

又因为环的线性基都是同一个,所以枚举线性基上的每一位,随便就能求出\(u,v\)的个数即可

需要处理哪些位置在线性基中

TLE了一次:因为图不一定联通,然后把我心态搞炸了

#include<bits/stdc++.h>
#define ll long long
const int p=1e9+7;
using namespace std;

const int N=1e5+5;
int n,m;
ll a[N],c[65];
namespace BCJ {
	int f[N];
	inline int find(int x) {
		int r=x,u;
		while(r!=f[r]) r=f[r];
		while(x!=f[x]) {
			u=x;x=f[x],f[u]=r;
		}
		return r;
	}
	inline void init() {
		for(int i=1;i<=n;i++) f[i]=i;
	}
}

struct A{int v; ll w; };
vector<A>V[N],V1[N];
struct B{int u,v; ll w;};
vector<B>E;
vector<int>Q;
int sz;
void dfs(int fa,int u) {
	sz++; Q.push_back(u);
	for(A v:V[u]) {
		if(v.v!=fa) {
			a[v.v]=a[u]^v.w;
			dfs(u,v.v);
		}
	}
	for(A v:V1[u]) {
		E.push_back((B){u,v.v,v.w});
	}
}

void ins(ll k) {
	for(int i=60;i>=0;i--) {
		if((1LL<<i)&k) {
			if(c[i]) k^=c[i];
				else {
					c[i]=k;
					return;
				}
		}
	}
}
bool vis[65];
int main() {
	scanf("%d%d",&n,&m);
	BCJ::init();
	for(int i=1;i<=m;i++) {
		int u,v; ll k; scanf("%d%d%lld",&u,&v,&k);
		if(BCJ::find(u)!=BCJ::find(v)) {
			BCJ::f[BCJ::find(u)]=v;
			V[u].push_back((A){v,k});
			V[v].push_back((A){u,k});
		} else {
			V1[u].push_back((A){v,k});
		}
	}
int ans=0;
for(int e=1;e<=n;e++) {
if(BCJ::find(e)==e) {
	sz=0,Q.clear(),E.clear();
	dfs(0,e);
//	printf("%d\n",sz);
	int num=0;
	memset(c,0,sizeof(c));
	for(B i:E) {
		ins(a[i.u]^a[i.v]^i.w); 
	}
	for(int i=60;i>=0;i--) if(c[i]>0) num++;
	memset(vis,0,sizeof(vis));
	for(int i=60;i>=0;i--) {
		if(c[i]) {
			for(int j=i;j>=0;j--) {
				if(c[i]&(1LL<<j)) vis[j]=1;
 			}
		}
	}
	for(int i=60;i>=0;i--) {
		if(vis[i]) {
			ans=((1LL<<i)%p*((1LL<<num-1)%p)%p*((((ll)sz*(sz-1))>>1)%p)+ans)%p;
		} else {
			int cnt=0;
			for(int j:Q) {
				if(a[j]&(1LL<<i)) cnt++;
			}
			ans=((1LL<<i)%p*((1LL<<num)%p)%p*cnt%p*(sz-cnt)+ans)%p;
			assert(sz>=cnt);
		}
	}
}
}
printf("%d\n",ans);
	return 0;
}

例题2

Luogu P4869 albus就是要第一个出场

显然和线性基有关,数总共\(2^n\),做出线性基,显然答案总共有\(2^k\)

假设有1个数能被其他数表示,则所有的答案都能异或上这个数和被表示的数,形成新的相同的答案,所以每个答案有\(2^{n-k}\)

所以只要知道其在线性基中的排名即可,即反向求第K大

按第K大处理后求出二进制数转成十进制即可
WA了一次,没想清楚排名怎么求

#include<bits/stdc++.h>
const int p=10086;
using namespace std;

const int N=1e5+5;
int n,c[N],a[N];
void ins(int k) {
	for(int i=30;i>=0;i--) {
		if((1<<i)&k) {
			if(c[i]) k^=c[i];
				else {
					c[i]=k;
					for(int j=i-1;j>=0;j--) {
						if((c[i]&(1<<j))&&c[j]) {
							c[i]^=c[j];
						}
					}
					for(int j=i+1;j<=30;j++) {
						if(c[j]&(1<<i)) c[j]^=c[i];
					}
					return;
				}
		}
	}
}
inline int ksm(int a,int b) {
	int ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		ins(a[i]);
	}
	int K; scanf("%d",&K);
	int num=0,Num=0,ans=0;
	for(int i=30;i>=0;i--) {
		if(c[i]) {
			Num++;
			ans=ans*2%p; 
			if(K&(1<<i)) {
				ans=(ans+1)%p;
			}
		}
	}
	printf("%d\n",(ans*ksm(2,n-Num)+1)%p);
	return 0;
}
posted @ 2021-04-06 10:07  wwwsfff  阅读(107)  评论(0)    收藏  举报