时间戳线性基

发布时间 2023-03-31 11:09:03作者: Xun_Xiaoyao

当我们需要维护和向量空间或者异或和有关的东西,可能会用到线性基:

提出问题

【模板】线性基

题意:给一个长为 \(n\) 的序列,问从中任取若干个数,最大的异或和为多少。

这个问题可以直接使用线性基维护,但是我们考虑将这个问题进行加强:

CF1100F Ivan and Burgers

题意:给一个长为 \(n\) 的序列,\(Q\) 次询问,问从 \(a_l,a_{l+1}\dots a_r\) 中选若干个数,异或和最大为多少。

查询从全局查变成了区间查。

如果使用线段树维护,时间复杂度是 \(O(n\log^2V+Q\log n\log^2V)\)
如果使用猫树维护,时间复杂度是 \(O(n\log n\log V+Q\log^2V)\)

深入研究

现在我们继续深究性质,看看能否做到更优的复杂度。

我们先想一想,为什么区间查的线性基不能用全局的维护,某种意义上也就是为什么线性基不支持删除。
因为我们并不知道线性基的每一位是由哪些数组成的,比如说,最高位可能是 \(a_1\oplus a_3\oplus a_4\),或者最低位是 \(a_{114}\oplus a_{514}\)
这就导致我们并不知道在查区间的时候,能不能选择某一位。

先假定我们的询问区间都是形如 \([l,n]\) 的,考虑如何处理。
我们考虑记录每一位中编号最小的数的下标 \(b\),比如我们上面那两个例子中,就是 \(1\)\(114\)
在处理询问 \([l,n]\) 是,我们就扫一遍,如果 \(b\geqslant l\),我们就按照正常的线性基进行处理,否则就跳过它。
所以,为了保证答案的正确性,我们要上每一位的 \(b\) 尽可能的大,来满足更多的 \(l\)
现在我们考虑加入一个数的时候如何保证每一位都得到了最大的 \(b\)
我们维护二元组,分别表示线性基插入的数和它的时间戳。
当这个数某一位为 \(1\) 的时候,考虑线性基中的当前位:如果为空,则直接插入;如果有数,且其时间戳更小,则交换这两个二元组,并将不在线性基中的数异或上另一个。
具体实现如下:

void insert(int a,int tm)
{
    for(int i=19;~i;i--)
    {
        if(a&(1<<i))
        {
            if(tim[i])
            {
                if(tim[i]<tm)
                {
                    swap(xxj[i],a);
                    swap(tim[i],tm);
                }
                a^=xxj[i];
            }
            else
            {
                xxj[i]=a,tim[i]=tm;
                return;
            }
        }
    }
    return;
}

这样子,我们只需要顺序将所有元素插入,并在每一次插入后处理的 \(r=i\) 的询问即可。
时间复杂度是 \(O((n+Q)\log V)\)

拓展延申

上述算法是离线的,那能不能在线呢?
由于线性基大小是 \(O(\log V)\) 的,所以我们每次处理都在前一个的复制上进行处理,就不会破坏前面的线性基了,时间复杂度仍然是 \(O((n+Q)\log V)\) 的。

上述算法是静态的,那能不能带修呢?
不知道,但感觉多半不行。