前缀线性基学习笔记:F. Ivan and Burgers

前言

众所周知,一个序列的线性基可以用于求解这个序列的子序列异或和最大值。
那么,如果要高效率的求解一个序列的子区间的子序列异或和最大值,就需要用到前缀线性基。

问题

给出一个序列 a 1 . . a n a_1..a_n a1..an, 长度为 n n n, 以及 q q q 个询问, 每个询问要求在 a L , . . , a R a_L,..,a_R aL,..,aR 内取任意的元素,使得异或和最大,求最大的异或值。
问题链接:https://codeforces.com/contest/1100/problem/F

问题本质是什么?

我们需要对于 a L . . a R a_L..a_R aL..aR, 构造出一个线性基,以求得哪些数位(2进制,下同)可以取1,哪些数位只能取 0。
如果对于每个询问,暴力地去构造线性基,那么最坏复杂度为 O ( n × q ) O(n\times q) O(n×q)
有没有效率更高的方法呢?

预处理前缀线性基

其实由于线性基只有30位,所以对于每一个位置 i ∈ [ 1 , n ] i \in [1,n] i[1,n], 我们都可以预处理前缀线性基。
p [ i , b ] p[i,b] p[i,b] 表示对于前 i i i 个数,第 b b b 位的线性基。
i d x [ i , b ] idx[i,b] idx[i,b] 表示能构成 p [ i , b ] p[i,b] p[i,b] 这个线性基的元素的下标最大值。
那么对于一个询问 L , R L,R L,R
也可以采取线性基求子序列异或和最大值相同的方法。
如果对于 i d x [ R , b ] idx[R,b] idx[R,b] , 大于等于 L L L, 说明 p [ R , b ] p[R,b] p[R,b]可以加入 答案。

一些有关前缀线性基插入的细节操作

处理完前 i − 1 i - 1 i1 个元素的前缀线性基,插入第 i i i 个元素时:
我们需要知道更新哪些 p [ i , b ]    i d x [ i , b ] ( 0 ≤ b ≤ 30 ) p[i,b]~~idx[i,b](0 \le b \le 30) p[i,b]  idx[i,b](0b30) ,哪些是从 p [ i − 1 , b ]    i d x [ i − 1 , b ] p[i - 1,b]~~idx[i - 1, b] p[i1,b]  idx[i1,b] 直接转移。
按照常规线性基的插入操作,从高位往低位考虑:
需要记录两个信息
v a l val val : 考虑到第 b b b 位, p [ i − 1 , b p[i - 1, b p[i1,b ~ 30 ] 30] 30] a i a_i ai 构成的待插入线性基。
p o s : pos: pos 构成 v a l val val 中, 下标最小的元素的下标。
对于每一位 b b b p o s ≥ i d x [ i − 1 ] [ b ] pos \ge idx[i - 1][b] posidx[i1][b], 那么就插入当前的 v a l val val, 即: p [ i , b ] = v a l p[i,b] = val p[i,b]=val
否则 p [ i , b ] = p [ i − 1 , b ]    i d x [ i , b ] = i d x p [ i − 1 , b [ p[i,b] = p[i - 1,b]~~idx[i,b] = idxp[i - 1,b[ p[i,b]=p[i1,b]  idx[i,b]=idxp[i1,b[
然后需要把 p [ i − 1 , b ] p[i-1,b] p[i1,b] 加入 v a l val val 中。即 v a l    X O R    p [ i − 1 , b [ → v a l val~~XOR~~p[i-1,b[ \to val val  XOR  p[i1,b[val
时间复杂度 O ( n × b i t s i z e ( a i ) )     b i t s i z e 表示 2 进制位数 O(n \times bitsize(a_i))~~~bitsize表示2进制位数 O(n×bitsize(ai))   bitsize表示2进制位数
参考代码

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 5e5 + 10;
int c[N];
int p[N][32];
int idx[N][32];
int n, m;
void inst(int val, int pos) {
    int tmpp = pos;
    int fl = 1;
    for (int i = 30; i >= 0; i -- ) {
        if (((val >> i) & 1) && fl) {
            if (tmpp > idx[pos - 1][i]) {
                idx[pos][i] = tmpp;
                p[pos][i] = val;
                tmpp = min(tmpp, idx[pos - 1][i]);
                val ^= p[pos - 1][i];
                if (p[pos - 1][i] == 0) fl = 0;
            } else {
                val ^= p[pos - 1][i];
                p[pos][i] = p[pos - 1][i];
                idx[pos][i] = idx[pos - 1][i];
            }
        } else {
            p[pos][i] = p[pos - 1][i];
            idx[pos][i] = idx[pos - 1][i];
        }
    }
}

int query(int l, int r) {
    int res = 0;
    for (int i = 30; i >= 0; i -- ) {
        //cout << idx[r][i] << endl;
        if (idx[r][i] >= l) {
            res = max(res, res ^ p[r][i]);
        }
    }
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;;
    for (int i = 1; i <= n; i ++ ) {
        int x;
        cin >> x;   
        inst(x, i);
    }   
    cin >> m;
    for (int i = 1; i <= m; i ++ ) {
        int l, r;
        cin >> l >> r;
        cout << query(l, r) << '\n';
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值