标签:space turn max 判断 line span return read har
线性基通常用于解决一系列关于异或的问题。
定义:线性基支持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; }
标签:space turn max 判断 line span return read har
原文地址:https://www.cnblogs.com/h-lka/p/11027581.html