题解 P3812 【【模板】线性基】

wrpwrp

2020-06-15 16:03:23

题解

upd:补了点东西

用处

没用我学这东西干嘛

这么点?
还有点性质在下面 可能有点用

性质

原理&实现

由于我想写简单一点,直接对代码写,所以真正的关于线性代数的那一部分就没写了 妄图掩盖自己不会的事实

约定

以下p[i]表示原序列的线性基数组。

构造

先贴代码 把一个数插入线性基:

inline void ins(LL x) {
    for(R int i=62;i>=0;i--)
        if(x&(1LL<<i)) {
            if(!p[i]) { p[i]=x,cnt++; break; }
            else x^=p[i];
        }
}

人话描述:

那么,通过观察这个构造,我们再来感性理解线性基。

于是我们初步理解了线性基

查询一个元素是否可以被异或出来

从高到低,如果这一位为1就异或上这一位的线性基,把1消去,根据性质一,如果最后得到了0,那这个数就可以表示出来。

inline int ask(LL x) {
    for(R int i=62;i>=0;i--) 
        if(x&(1LL<<i)) x^=p[i];
    return x==0;
}

查询异或最大值

按位贪心即可。

inline LL askmx() {
    LL ans=0;
    for(R int i=62;i>=0;i--)
        if((ans^p[i])>ans) ans^=p[i];
    return ans;
}

查询异或最小值

其实异或的最小值一般来说就是线性基里的最小元素,因为插入这个元素的时候我们总是尽量让它的高位为0才来插入这一位。但是为什么是“一般”呢?因为有可能会有出现0,得要在插入的时候记下个标记来特判才行。

inline LL askmn() {
    if(zero) return 0;
    for(R int i=0;i<=62;i++)
        if(p[i]) return p[i];
}

查询异或第k

这个东东我感觉实现还是有那么点点复杂的哈。 首先考虑,要是每一位的选择都不会影响下一位的话,那就可以直接从高到低按位去选择就行了,就类似于二叉树求rank的玩法。但是我们之前建出来的线性基是没有这个性质的。比如p[3]=101_{2},p[0]=1_{2}的时候就炸了。所以我们考虑重构一个数组d来解决这个问题。先上代码:

inline void rebuild() {
    cnt=0;top=0;
    for(R int i=MB;i>=0;i--)
        for(R int j=i-1;j>=0;j--)
            if(p[i]&(1LL<<j)) p[i]^=p[j];
    for(R int i=0;i<=MB;i++) if(p[i]) d[cnt++]=p[i];
}

那么这是在干啥呢,就是在尽力把每个p[i]只留下第i位的1,从而使得各个位之间互不影响,也就是说它的理想效果如下:

p[0] ~0~0~0~0~1 p[1] ~0~0~0~1~0 p[2] ~0~0~1~0~0 p[3] ~0~1~0~0~0 p[4] ~1~0~0~0~0

但有时候并不能达到这个样子,可能会出现如下情况:

p[0] ~0~0~0~0~1 p[1] ~0~0~0~0~0 p[2] ~0~0~1~0~0 p[3] ~0~1~0~0~0 p[4] ~1~0~0~1~0

但是这个时候我们注意到,我们的目的已经达到了,互不影响的任务已经达成了,显然此时为0p值不会对排名有任何影响,不用管它了。把信息导入到d数组后,查询k小代码不难写出:

inline LL kth(int k) {
    if(k>=(1LL<<cnt)) return -1;
    LL ans=0;
    for(R int i=MB;i>=0;i--)
        if(k&(1LL<<i)) ans^=d[i];
    return ans; 
}

其实我个人觉得这个代码还得要自己理解一下。
背板子也行 但是这样其实还不太对,因为我们并没有考虑0的情况,所以还要去考虑一下0的情况,特判即可。

printf("%lld\n",tmp-zero?kth(tmp-zero):0LL);

查询排名

inline int rank(LL x) {
      int ans = 0;
      for(R int i = cnt - 1; i >= 0; i --)
                  if(x >= d[i]) ans += (1 << i), x ^= d[i];
      return ans + zero; 
}

注:这个d[i]是重建后的线性基。

于是线性基的基本操作就结束啦!

习题

  1. Luogu P3812 线性基 code

  2. Luogu P3857 [TJOI2008]彩灯 code

  3. Luogu P4301 [CQOI2013]新Nim游戏 code

  4. Luogu P4570 [BJWC2011]元素 code

  5. HDU 3949 XOR code

  6. Luogu P4151 [WC2011]最大XOR和路径 code

  7. Luogu P3292 [SCOI2016]幸运数字 code

    EX

    dalao博客