Loading

【学习笔记】线性基

Page Views Count

指异或线性基,用一组线性无关的基底表示一些数异或的信息。

性质

  • 任意一个原序列中的数度都能被线性基的几个基底异或得到。

  • 线性基任意一个子集异或和不为 \(0\)

  • 一个序列对应多个线性基,但线性基的规模都一定,且是满足性质 \(1\) 中所有基底组成集合最小的一个。

证明将在插入操作中展开。

具体操作

插入

对于数 \(x\),从高到低位枚举,如果这一位是 \(1\),且目前这一位的基底还没有,就直接更新基底并退出;反之将 \(x\) 异或这一位的基底。

inline void insert(ll x){
    for(int i=62;i>=0;--i){
        if(x&(1ll<<i)){
            if(!bs[i]){
                bs[i]=x,++cnt;
                break;
            }
            else x^=bs[i];
        }
    }
    chk0=1;
}

对于性质 \(1\),这样插入如果最终也没有更新过一个基底,那么一定是被异或为 \(0\),也就是被表示出来了,反之相当于更新的基底异或上之前的位置,也得到了这个数。

对于性质 \(2\),如果出现异或和为 \(0\),那么最后一个数不会被插入。

对于性质 \(3\),设想仅改变两个数 \(x,y\) 的插入顺序,而构成的线性基不同,那么一定会在一位产生分歧,即这一位被先插入的数 \(x\) 占据,讨论 \(y\) 最后是否被插入,如果没有被插入,则没有任何影响;如果被插入,那么原来插入在这个位置的数将会被顶替,就变成了又一个这样的问题,只不过是位置变低,依此归纳,最终最后一位被占据的将无法插入,也就是线性基规模是不变的。

查询是否可以被表示

按照插入方法,判断最终是否为 \(0\) 即可。

inline bool query(ll x){
    for(int i=62;i>=0;--i){
        if(x&(1ll<<i)) x^=bs[i];
    }
    return !x;
}

查询异或最大值

接下来我们的操作都等价到线性基上,线性基的每一个基底最高位都不同,这是很友好的性质。

从高到低枚举,这样多异或一个数不会改变更高位的值,于是贪心异或,如果可以变更大就异或。

inline ll query_max(){
    ll res=0;
    for(int i=62;i>=0;--i){
        if((res^bs[i])>res) res^=bs[i];
    }
    return res;
}

查询异或最小值

如果可以出现 \(0\),答案必然是 \(0\),反之答案应该是最小的基底,因为只要有更大的基底参与,那么一定会在更高位上有 \(1\),而大于这个数。

inline ll query_min(){
    if(chk0) return 0;
    for(int i=0;i<=62;++i){
        if(bs[i]) return bs[i];
    }
}

查询 \(k\) 小值

相对较难。思考这样一个问题:如果基底都是 \(2^k\),的确满足了完全表示的性质,并且这种中规中矩的基底,可以直接根据 \(k\) 的二进制来得到,也就是说,不会因为多选取一个元素而导致结果更小,或者是说,基底互不干扰。

但这样不满足线性基最小规模,例如 \((1011)_2,(0001)_2\),两个基底 \((1010)_2,(0001)_2\) 就满足了互不干扰和规模最小两个条件。

如何构造这种互不干扰的线性基?

实际上是对于一个高位基底,尽量地消去低位的 \(1\),使得一个基底的最高位只在自己位置是 \(1\),其余基底均为 \(0\)。这样的得到满足线性基性质却又可以进行上述查询的基底。

接下来类似于变进制转换,直接把 \(k\) 中对应位是 \(1\) 的基底异或起来即可。

inline void rebuild(){
    for(int i=62;i>=0;--i){
        for(int j=i-1;j>=0;--j){
            if(bs[i]&(1ll<<j)) bs[i]^=bs[j];
        }
    }
    for(int i=0;i<=62;++i){
        if(bs[i]) rbs[++tot]=bs[i];
    }
}
inline ll kth(ll k){
    if(chk0){
        if(k==1) return 0;
        --k;
    }
    ll res=0;
    for(int i=tot;i>=1;--i){
        if(k&(1ll<<i-1)) res^=rbs[i];
    }
    return res;
}

参考资料

posted @ 2022-12-27 11:07  SoyTony  阅读(174)  评论(0编辑  收藏  举报