AcWing_3485 最大异或和(前缀和、滑动窗口、贪心、Trie)

AcWing

3485. 最大异或和

异或的性质:

$a{}^\wedge b{}^\wedge b = a$.

$a_k{}^\wedge a_{k+1}{}^\wedge \cdots {}^\wedge a_n = (a_1 {}^\wedge a_2 {}^\wedge \cdots {}^\wedge a_k{}^\wedge \cdots {}^\wedge a_n) {}^\wedge (a_1 {}^\wedge a_2 {}^\wedge \cdots {}^\wedge a_{k - 1})$,即$s[k, n] = s[n] - s[k - 1]$. 使用前缀和。

枚举区间的右端点$i\in [1, n]$,其则左端点$l\in [i - m + 1, i]$,$l - 1\in [i - m, i - 1]$,特殊地,当$i - m < 1$时,令$i - m = 0$,则$s[l, i] = s[i] - s[l - 1]$. 即滑动窗口。

在每次枚举右端点及窗口范围时,需要取得当前窗口内最大的区间异或和。如果再在窗口内枚举左端点,其时间复杂度为$O(mn)$,超时。所以使用Trie(字典树)维护窗口内元素值的方式。对于给定的右端点值,从其二进制的高位到低位,取Trie中的位相反值(如果有),即贪心,最终将取出窗口内的最大区间异或和。其时间复杂度为$K\cdot O(n)$.

#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N1 = 100010;
const int N2 = 100000 * 31 + 10;
// trie,模板
int idx = 0;
int s[N1], trie[N2][2], cnt[N2];
void insert(int x, int v){
    int p = 0;
    for(int i = 30; i >= 0; i--){
        int u = x >> i & 1;
        if(!trie[p][u]) trie[p][u] = ++idx;
        p = trie[p][u];
        cnt[p] += v;
    }
}
int query(int x){
    int p = 0, res = 0;
    for(int i = 30; i >= 0; i--){
        int u = x >> i & 1;
        if(cnt[trie[p][!u]]){
            p = trie[p][!u];
            res = res * 2 + 1;
        }
        else{
            p = trie[p][u];
            res = res * 2;
        }
    }
    return res;
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> s[i];
        s[i] = s[i] ^ s[i - 1];
    }
    int res = 0;
    insert(s[0], 1);
    for(int i = 1; i <= n; i++){
        if(i - m >= 1) insert(s[i - m - 1], -1);
        res = max(query(s[i]), res);
        insert(s[i], 1);
    }
    cout << res << endl;
    return 0;
}