线性基
分析
用于解决异或问题,可以表示异或的所有值
删除
在线
-
如果在线性基外,直接删
-
如果在线性基内
- 如果可以有代替的数,删掉代替的
- 如果没有,找到最小的被他异或过的数(存在传递关系),将其依次异或被他异或过的数(消除影响)
时间复杂度\(O(n+lgn)Q\)
离线
线段树分治
求交并
求并
暴力插入,时间复杂度\(O(nlg^2n)\)
求交
https://blog.csdn.net/djyanglinhan/article/details/88825831
例题1
显然对于每组\(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
显然和线性基有关,数总共\(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;
}