线性基浅谈

不知道最初是从哪个角度发现的

蒟蒻我最初是从玄学的线性代数与向量思想开始学的,然后因为不会高斯消元转入另一种。

其实对于线性基来说,只要搞懂一个关系式子: axorb=c,cxora=b,bxorc=a; a x o r b = c , c x o r a = b , b x o r c = a ;
也就是说,知二求一。那么将一堆数合成一个线性基,应用的也是这个道理;

线性基长什么样

就是一个一维数组, a[i] a [ i ] 等于的数满足该数二进制表示下,最高有一的那一位为i;比如 a[3]=(110)2 a [ 3 ] = ( 110 ) 2

下面具体讲操作
  1. 给你一个线性基,给你一个数,问这个数是否属于该线性基的张成。

    最简单的想法:2^n枚举;但这显然过于暴力。

    这样想:如果属于,那么用线性基异或出来的数异或这个数等于0。我们只要让这个数去异或线性基中的数,使它竭力靠近零。

    如何竭力靠近0?

    举个例子:一个数为 (1011001101)2 ( 1011001101 ) 2 ,然后发现了第十位为一。最终结果第十位不为1,肯定要异或掉就拿 a[10] a [ 10 ] 来异或。这样就对10位以上的更高位保持为0没有影响.然后再看第九位.

    唯一值得注意的,本身是零要特判。

  2. 给你一堆数构造他们的线性基。

    这一个操作实际上结合了定义之后就变得简单。只要你加入的这一个数有一的最高位在原线性基中还没有出现,直接赋值即可。如果出现了,那么就与这一位异或一下。

    讲的可能不太清楚,上一份代码就懂了

    for(int i=1;i<=n;i++) {
      for(int j=62;j>=0;j--) {
      if(!(a[i]>>j)) continue;//对线性基的这一位没有贡献
         if(!p[j]) { p[j]=a[i]; break; }//选入线性基中
           a[i]^=p[j];
         }
       }

  3. 给你一个线性基要你求能构造出来的第k小

    这一个的做法其实有点利用二进制思想。如果现在有 (1)2,(10)2,(100)2,(1000)2,(10000)2,(100000)2.... ( 1 ) 2 , ( 10 ) 2 , ( 100 ) 2 , ( 1000 ) 2 , ( 10000 ) 2 , ( 100000 ) 2 . . . . 这些数,要你构造他们异或和的第k大,你可能会觉得这太容易了。这是什么原理呢??掘其本质,你会发现他们无论怎么异或,原有位置上的1永远不会异或变成零,这又是由于如果他们有一位为一,其它数这个位上绝对不为一。如果我们的线性基有这一个性质就好了。也就是说,按定义 a[i] a [ i ] 表示有一的最高位是 i i ,那如果其余的任意a[k],k>i的第i位都为0,那就满足了 a[i] a [ i ] 最高位上的i怎么也不会异或成零。

    现在最重要的一点是如何构造?假设现在这个线性基已经构造好了,由于对于 a[i],a[k],i>k a [ i ] , a [ k ] , i > k , a[k] a [ k ] 显然不会影响 a[i] a [ i ] 只要扫两遍就行了

    上个代码

    il void rebuild(){
       for (RG ll i=62;i>=0;--i)
       for (RG ll j=i-1;j>=0;--j)
           if (p[i]>>j&1) p[i]^=p[j];
       for (RG ll i=0;i<=62;++i) if (p[i]) [cnt++]=p[i];
      return;
    }
    
    il ll query_kth(RG ll k){
         if (k>=(1LL<<cnt)) return -1; RG ll    res=0;     for (RG ll i=62;i>=0;--i)
       if (k>>i&1) res^=d[i];
      return res;
    }
  4. 最后讲一讲个人搞出来的时间戳线性基。

    如果有 n n 个数(a1,a2,a3,a4,.....,an)依次加入线性基,在加入过程中,不断询问,区间 [l,r] [ l , r ] 的数能异或出来的最大的数是多少。遮盖怎么做?

    这就需要再次仔细研究线性基的性质。这次我们要考虑加入的时间。现在加入 a,b,c,d,e a , b , c , d , e 五个数,加入到d时,询问 b,c,d b , c , d 能够异或的最大数是多少。

    这时就显然不能考虑 a a 的影响。在这里还要插入一个小技巧,就是把询问区间按左端点与右端点排序。这样你的询问就是有序的了(嗯,这个方法真的常用)。

    我们这样做后 显然 要使得

    • 排除线性基中所有被 a 影响了的数

    • 剩下的这些数中是 b,c,d b , c , d 的线性基
    • 我们的任务是这两点。乍一看,不可能实现,实际上由于加入是一个不可逆的过程,我们无法用线性基去删除一个元素,而询问区间是有序的,对每一个线性基中的数打上一个时间戳就显得尤为重要。

      我们不妨设线性基中的每一个数都有一个时间戳 d d 这个d代表的意义是:影响该位的最小位置为 d d <script type="math/tex" id="MathJax-Element-759">d</script>
      有了这个时间戳后,被小位置更新过的一定不包含被大位置更新过。
      每次加入一个数后,直接处理以该数位置为右端点的所有询问。
      详细见代码

#include<bits/stdc++.h>
using namespace std;
inline char gc(){
    static char buf[1<<6],*p1=buf,*p2=buf;
    return (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<6,stdin),p1==p2))?EOF:*p1++;
}
template <class T>
inline void read(T&data){
    register char ch=0;
    data=0;
    while(ch<'0'||ch>'9')ch=gc();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch^48);
        ch=gc();
    }
    return ;
}
const int _ (1e6+5);
int n,Time[_],L[_],head[_],nt[_],a[_],poww[_],Xor[32],ans[_],m;
int main(){
    freopen("e.in","r",stdin);
    read(n);
    read(m);
    for(register int i=1;i<=n;++i){
        read(a[i]);
    }
    for(register int i=1,R;i<=m;++i){
        read(L[i]);
        read(R);

        nt[i]=head[R],head[R]=i;
    }


    for(register int i=1;i<=4;++i){
        register int I=i;
        for(register int j=30;~j;--j){

            if((1<<j)&a[i]){
                if(!Xor[j]){
                    Xor[j]=a[i];
                    Time[j]=I;
                    break;
                }
                if(Time[j]<I){
                    swap(Time[j],I);
                    swap(Xor[j],a[i]);

                }
                a[i]^=Xor[j];
            }
        }
        for(register int j=head[i];j;j=nt[j]){

            for(register int k=30;~k;--k){
                if(Time[k]>=L[j])ans[j]=max(ans[j],ans[j]^Xor[k]);
            }
        }
    }
    for(register int i=1;i<=m;++i){
        cout<<ans[i]<<endl;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值