线性基浅谈
Posted h-lka
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性基浅谈相关的知识,希望对你有一定的参考价值。
线性基通常用于解决一系列关于异或的问题。
定义:线性基支持log插入,用一个数组来存储线性基,其中,第i个线性基就是第i位是1的数。
由此,给出插入线性基的板子。
void insert(int x) for(int i=31;i>=0;i--) if(!(x&(1<<i)))continue; if(!p[i]) p[i]=x; break; x^=p[i];
其中,(x&(1<<i))是用来取出x的第i位的。
如果是0,则跳出,匹配失败。如果是1,则继续匹配。
记住要有break。
由此,可以得出线性基的三个性质:
1.原序列中的任意数都可以由线性基中的数异或得到。
2.线性基中的数异或起来不能为0.
3.线性基中的数在性质1的条件下,数量是唯一的且最少的。
证明性质1:
当插入失败时,则有x^a[1]^a[2]...=0,即a[1]^a[2]^...=x.
所以,线性基序列中一定可以通过异或得到原序列中的x.
当插入成功时,可得知,x在前面一定异或了一些数,即:
x^a[1]^a[2]....=a[i].
所以,a[1]^a[2]^...^a[i]=x.
综上所述,性质1得证。
证明性质2:
设p[1]^p[2]^p[3]=0,(下标是加入顺序)
则有p[1]^p[2]=p[3],即p[3]不可能被加入线性基。
综上所述,性质2得证。
证明性质3:
首先,如果序列中所有元素都可以被加入线性基,显然性质3成立。
当有些数(x)不可以加入时,则有:
设p[1]^p[2]^p[3]=x,
则有p[1]^p[2]^x=p[3].
也就是说,当你改变插入顺序,只会改变插入的数,数量不会改变。
综上所述,性质3得证。
例1:模板题(点击标题即可进入题目)
题目大意不多说。正常插入,对于每一个数,求出它与其它数的最大异或值即可。
当然要用线性基啦。
代码:
#include<cstdio> #include<iostream> #include<cstring> #include<string> using namespace std; typedef long long ll; ll p[101],a[51]; ll ans,n; inline void read(ll &x) ll s=0,w=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) if(ch==‘-‘)w=-1; ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘) s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); x=s*w; inline void get(ll x) for(int i=62;i>=0;--i) if(!(x>>(ll)i))continue; if(!p[i]) p[i]=x; break; x^=p[i]; int main() read(n); for(ll i=1;i<=n;++i)read(a[i]);get(a[i]); for(int i=62;i>=0;i--) if((ans^p[i])>ans) ans^=p[i]; printf("%lld\n",ans); return 0;
注意最后的ans,依次比对,记住加括号。
例题2:元素(点击标题即可进入题目)
题意就是,每个元素有两个值,一个标号,一个贡献,当且仅当前面选中的物品的标号无法通过异或得到当前物品标号时,当前物品才能选。
那么,考虑贪心。
对于初始序列,来一发sort快排,以价值为关键字。之后,遍历序列,插入每个数的标号判断是否合法,可以在插入时判断。插入后,若合法,则记录贡献,否则pass.
本题考虑性质3的理解。
代码:
#include<cstdio> #include<iostream> #include<cstring> #include<string> #include<algorithm> using namespace std; typedef long long ll; ll p[101]; ll ans,n; inline void read(ll &x) ll s=0,w=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) if(ch==‘-‘)w=-1; ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘) s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); x=s*w; inline bool get(ll x) for(int i=62;i>=0;--i) if(!(x>>(ll)i))continue; if(!p[i]) p[i]=x; return true; x^=p[i]; return false; struct node ll x,y; a[500000]; bool cmp(node q,node w) return q.y>w.y; int main() read(n); for(ll i=1;i<=n;++i)read(a[i].x);read(a[i].y); sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++) if(get(a[i].x))ans+=a[i].y; printf("%d\n",ans); return 0;
例3:P哥的桶(点击标题即可进入题目)
题目中要求做到区间查询,单点插入(修改),异或和最大。
考虑线段树维护线性基。
对于每一个节点都维护自己区间的线性基。对于插入,线性基插入即可。
对于查询,我们随时开一个外部结构体(同样类型),每次把合法区间的线性基合并,最后在这里查询即可。
(我还是太菜了,涨知识了qwq)
代码:
#include<cstdio> #include<iostream> #define MAXN 50000 #include<cstring> using namespace std; int n,m,opt,k,x; struct node int p[50]; void insert(int x) for(int i=31;i>=0;i--) if(!(x&(1<<i)))continue; if(!p[i]) p[i]=x; break; x^=p[i]; void Ins(node &n) for(int i=31;i>=0;i--) if(n.p[i])insert(n.p[i]); tr[MAXN<<2],ans; inline void Insert(int cur,int l,int r,int k,int x) tr[cur].insert(x); if(l==r)return; int mid=(l+r)>>1; if(k<=mid)Insert(cur<<1,l,mid,k,x); else Insert(cur<<1|1,mid+1,r,k,x); inline void query(int x,int l,int r,int ql,int qr) if(ql<=l&&qr>=r)ans.Ins(tr[x]);return; int mid=(l+r)>>1; if(qr<=mid)query(x<<1,l,mid,ql,qr); else if(mid<ql)query(x<<1|1,mid+1,r,ql,qr); else query(x<<1,l,mid,ql,mid),query(x<<1|1,mid+1,r,mid+1,qr); int main() scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d%d%d",&opt,&k,&x); if(opt==1) Insert(1,1,m,k,x); else memset(ans.p,0,sizeof(ans.p)); query(1,1,m,k,x); int maxn=0; for(int i=31;i>=0;i--) if((maxn^ans.p[i])>maxn) maxn^=ans.p[i]; printf("%d\n",maxn); return 0;
以上是关于线性基浅谈的主要内容,如果未能解决你的问题,请参考以下文章