线性基学习笔记

好厉害的东西。


线性基可以在 \(O(\log n)\) 的空间内,维护 \(n\) 个数的异或值。

线性基有如下性质:

  • 原数列里的任何一个数都可以通过线性基里的数异或表示出来
  • 线性基里任意一个子集的异或和都不为 \(0\)
  • 一个数列可能有多个线性基,但是线性基里数的数量一定唯一,而且是满足性质一的基础上最少的

构造

\(p[i]\) 代表线性基第 \(i\) 位存的值。

插入一个数 \(x\),从高到低枚举每个二进制位。

  • 如果当前位 \(x\) 值为 \(0\) ,跳过 先不管
  • 如果当前位 \(x\) 值为 \(1\)
    \(1.p[i]=0:\)\(x\) 插入 \(p[i]=x\) 直接结束。
    \(2.p[i]\not=0:\) \(x→x\ \text{xor}\ p[i]\)

可以发现 这样到最后 要么 \(x\) 被插入
要么到最后 \(x=0\) ,说明 \(x\) 可以被线性基中的元素表示出来

inl void insert(int x){
    for(int i=52;(~i)&&x;i--){
        if(x&(1ll<<i)){
            if(!p[i]){p[i]=x;break;}
            else x^=p[i];
        }
    }
}

查询

异或最值

最小值比较简单,答案就是线性基中最小的数,因为它异或其他值一定都会变大。

inl int query_min(){
    for(int i=0;i<=52;i++)
        if(p[i])return p[i];
    return -1;
}

最大值直接贪心即可
从高到低枚举每一位,如果 \(ans\) 此时为 \(0\)\(p[i]\not=0\) 那么选上 这一位一定变\(1\) 答案一定会变大
但其实不用这么麻烦 直接看异或之后答案是否变大即可。

inl int query_max(){
    int ans=0;
    for(int i=52;~i;i--)
        ans=max(ans,ans^p[i]);
    return ans;
}

是否存在几个数异或和为 \(x\)

如果 \(x\) 的这一位有值 就把这一位异或掉。

最后变0了就赢了。

inl bool check(int x){
    for(int i=52;~i;i--)
        if(x&(1ll<<i))x^=p[i];
    return !x;
}

查询异或第 \(k\) 小/查询排名

首先 我们需要进行如下rebuild操作:

inl void rebuild(){
    for(int i=52;~i;i--)
        for(int j=i-1;~j;j--)
            if(p[i]&(1ll<<j))p[i]^=p[j];
    for(int i=52;~i;i--)
        if(p[i])d[cnt++]=p[i];
}

不难发现 经过如下处理后 元素总数不会变 而每一位最多只有一个元素是 \(1\)

也就是说,线性基中每两个元素都是互不影响的

那么我们还是直接贪 由于所有小的合起来都比一个大的小 不难写出如下代码:

inl int kth(int k){
    int ans=0;
    for(int i=cnt-1;~i;i--)
        if(k&(1ll<<i))ans^=d[i];
    return ans;
}
inl int rank(int x){
    int ans=0;
    for(int i=cnt-1;~i;i--)
        if(x>=d[i])ans+=(1ll<<i),x^=d[i];
    return ans+zero;
}

对了注意没插入的元素会贡献0(

posted @ 2024-01-23 15:28  xiang_xiang  阅读(22)  评论(0)    收藏  举报