线性基学习笔记
前言:
今天模拟赛T3是个线性基,发现自己没学,就赶紧来学了
这辈子第一篇博客纪念纪念
线性基能解决的问题:
求一个集合里选一些数异或的最大/最小值/第k大
构造线性基:
我们要构造一个线性基序列p,使它其中的数的异或和能包含原序列异或和的所有情况
比如插入一个数x:首先从高位到低位枚举x的二进制位i,第i位是1
1:如果p[i]没有值,p[i]=x,停止
2:如果p[i]有值,x^=p[i],接着往后枚举i
什么意思呢:要想让线性基序列把x的这一位i构造出来,最直接的就是让p[i]=x
但是如果以前有值,说明x的第i位已经可以构造出来,只需要接着构造x^p[i]
void add(int x){
for(int i=62;i>=0;i--){
if((x>>i)&1){
if(!p[i]){p[i]=x;break;}
else x^=p[i];
}
}
}
单次插入的复杂度O(log(x))
查询最大异或和:
我们贪心地考虑:先选位更高的,每一位的数只选一次(选两次就成0了)
所以直接从高位到低位枚举,如果异或上这个数使答案更大,就异或上
int querymx(){
int ans=0;
for(int i=62;i>=0;i--){
if((ans^p[i])>ans)ans^=p[i];
}
return ans;
}
单次查询复杂度O(log(x))
查询最小异或和:
依旧贪心地考虑:直接选最小的,因为它异或上任何数都会更大
int querymn() {
if(zero)return 0;
for(R int i=0;i<=62;i++){
if(p[i])return p[i];
}
}
现在我们会了普通的线性基求【1-n】的最大异或和,如果是求【l-r】的呢?
前缀线性基:
我们需要再多维护一个id数组,表示p[i]存的数的下标
先处理出【1-r】的线性基数组
插入元素:
依旧p[i]没有值时,p[i]=x,停止
但是当p[i]有值时,判断id[i]与当前插入元素下标pos大小
如果pos>id[i],需要swap(p[i],x),swap(id[i],pos),x接着往后构造
即当前p[i]始终维护下标最靠右的x,因为更靠右的位置下标>=l并成为答案的可能性更大
当然我们依然要使线性基数组能维护所有可能的异或和
所以使原来的p[i]成为新的x接着把它后面的构造出来
void add(int x,int pos){
for(int i=62;i>=0;i--){
if((x>>i)&1){
if(!p[i])return p[i]=x,id[i]=pos,void();
if(pos>id[i])swap(p[i],x),swap(id[i],pos);
x^=p[i];
}
}
}
查找【l~r】的最大异或和:
因为我们刚才维护了每一位的id,就很简单了
只需要在原来的基础上加上判断id[i]>=l成立才能转移
int querymx(int l){
int ans=0;
for(int i=62;i>=0;i--){
if(id[i]>=l&&(ans^p[i])>ans)ans^=p[i];
}
return ans;
}
然后我们又会了前缀线性基
最后我们来看这道模拟赛的线性基:子集
题目就是给一个数组a让你求 \(\sum\limits_{1<=l<=r<=n}\)(l~r的最大异或和)
1<=n<=1e5,0<=a[i]<=2^30
最暴力的就是先枚举1<=r<=n每次插入a[r]
再枚举1<=l<=r求querymx再加起来
复杂度显然\(O(n^2*30)\)
考虑优化:
由瞪眼观察可知:
当\(r\)固定时有好多l~r的最大异或和是相同的并且这些l一定是连续的
而且\(1<=l<=r\) [l~r]的最大异或和是非严格单调递减的
所以我们将最大异或和相等的l分成一段一段
而它们的分界点正是现在存的id排个序,因为现在的每一个id都会对答案有贡献,l每往右移跨过一个id贡献就会减少
所以每次开个数组把id存下来排个序,答案就是所有的id的答案*到上一个id的长度
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+7;
#define int long long
int p[N],id[N],n,top,st[N];
void add(int x,int pos){
for(int i=62;i>=0;i--){
if((x>>i)&1){
if(!p[i])return p[i]=x,id[i]=pos,void();
if(pos>id[i])swap(p[i],x),swap(id[i],pos);
x^=p[i];
}
}
}
int querymx(int l){
int ans=0;
for(int i=62;i>=0;i--){
if(id[i]>=l&&(ans^p[i])>ans)ans^=p[i];
}
return ans;
}
signed main(){
cin>>n;
int ans=0;
for(int r=1,v;r<=n;r++){
cin>>v,add(v,r);
top=0;for(int i=62;i>=0;i--)if(p[i])st[++top]=id[i];
sort(st+1,st+top+1);
for(int i=1;i<=top;i++)ans+=querymx(st[i])*(st[i]-st[i-1]);
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号