异或线性基

线性基

线性空间下的一组基

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

异或线性基的构造

考虑贪心

对于插入数 p ,如果 px 位为 1

ax 为空,则 ax=p

否则 p=pax

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 位遍历线性基 ,如果 ansai>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×logV)

P4151 [WC2011] 最大XOR和路径

链接

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

Solution

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

如果选这个环,则 sum=sumwcw=sumc

我们把环全放到线性基里

最后求最大异或和即可。

Q: 1n 的路径有很多条怎么办?

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

复杂度

O((m+n)×logV)

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,求从中间选择非空子集,使它们的乘积等于某个整数的平方的方法的数量

n105,ai70

Solution:

由于 ai 很小,可以对每个质数构造一维线性基

质因数分解 ai=pici

bi=i=1pcnt(cimod2)×2i

bi 插入线性基,可以发现当 bi 的异或和等于 0 就有一组解

不在线性基的 nsize 个数随便选,所以最后答案是 2nsize

复杂度 O(n×VlogV)

#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 求从 LR 的这 RL+1 个数中能选出多少个不同的子集,满足子集中所有的数的乘积是一个完全平方数。特别地,空集也算一种选法,定义其乘积为 1

思路

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

如果一个数有一个大于 n 的质因子,那么它一定不会有第二个大于 n 的质因子,所以我们只要把一个数小于 n 的质因子筛去,单独做大于 n 的质因子的线性基,最后复杂度是 354×(rl)1010 ,还是无法通过。

发现只要 lr 有一个数有 p 这个质因子,那么一定可以构成 p 的线性基,对于判断 lr 之间是否有 p 这个因子,只要有 (rp)>(l1p) ,那么一定有 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(mlognlogV2)

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,其中 bi=ai1ai , b1=a1

维护 bi 的线性基,在查询 l ,r 时,取出区间 l+1,r 的线性基,并 insert 一个 al

最后的线性基与 al ~ ar 是相同的

特判 l==r 即可

对于区间修改 b 数组,就只用改 blbr+1

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

维护 al 可以用线段树或树状数组维护

复杂度 O(m×logn×logV2)

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

链接

求区间最大异或和

n5×105

Solution1:

直接线段树暴力合并, 3log

Solution2:

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

我们记录线性基第 i 位为 ai ,下标为 posi

如果 posi<ll ~ 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;
}

那怎么让 posi 尽可能小呢

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

pi 位为 1 且已经有 ai

如果 x>posi 我们 swap(x,posi) , swap(p,ai)

swap 后再将p=pai

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

时间复杂度 O((n+m)×logV)

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

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;
}

END

posted @   yzq_yzq  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示