可持久化01trie

可持久化成都踹

前置

我们知道,我们可以使用 01trie 来求一个类似这样一个问题:

给一个 \(n\) 元数组 \(a\),给出一个数 \(x\)\(\max_{1 \le i \le n}\{x \oplus a_i\}\)

我们可以给 \(a\) 数组由高位到地位建一个 01trie,并把数组中每个数都插入进去。每次给出一个 \(x\),从 trie 的根开始,如果能向和 \(x\) 的当前位不同的子树走,就向那边走,否则往反方向。显然这样贪心是正确的。判断能不能向 \(x\) 当前位走,只需看其对应子树的 \(siz\) 是不是为 \(0\) 就行,是 \(0\) 就走不了(因为不存在这样的一个数)。

正题

我们将问题变换一下:

给一个 \(n\) 元数组 \(a\),给出 \(l, r, x\)\(\max_{l \le i \le r}\{x \oplus a_i\}\)

注意到这是套了一个区间限制。我们想到类比 P3834 【模板】可持久化线段树 2,将 01trie 可持久化并且求前缀和差分。

可持久化 01trie 显然是类比主席树的。我们可以十分容易的写出代码:

const int limit = 1e7 + 5 * 1e6, maxk = 24;
class per_01trie {
private:
    int ch[limit][2], siz[limit], tot = 0;
    void push_up(int i) { siz[i] = siz[ls(i)] + siz[rs(i)]; }
    void copy(int i, int ii) {
        ls(i) = ls(ii), rs(i) = rs(ii);
        siz[i] = siz[ii];
    }
public:
    void build(int &rt) {
        rt = tot = 1;
        ls(rt) = rs(rt) = 0;
        siz[rt] = 0;
    }
    void insert(int &i, int ii, int x, int dep = maxk) {
        i = ++tot;
        copy(i, ii);
        if(dep < 0) { 
            siz[i]++;
            AC; 
        }
        int k = (x >> dep) & 1;
        insert(ch[i][k], ch[ii][k], x, dep - 1);
        push_up(i);
    }
    void del(int &i, int ii, int x, int dep = maxk) {
        i = ++tot;
        copy(i, ii);
        if(dep < 0) { 
            siz[i]--;
            AC; 
        }
        int k = (x >> dep) & 1;
        del(ch[i][k], ch[ii][k], x, dep - 1);
        push_up(i);
    }
    il int query(int i, int j, int x, int dep = maxk) {
        if(dep < 0) return 0;
        int k = ((x >> dep) & 1) ^ 1;
        if(siz[ch[i][k]] - siz[ch[j][k]]) return query(ch[i][k], ch[j][k], x, dep - 1) + (1ll << dep);
        else return query(ch[i][k ^ 1], ch[j][k ^ 1], x, dep - 1);
    }
} ds;

例题

P4735 最大异或和

思路

如果直接用可持久化 01trie 是不好做的,因为每次 A 操作都会导致重新建树,考虑转换。

\(sum_i = a_1 \oplus a_2 \oplus \dots \oplus a_i\) 我们注意到:

\[a_p \oplus a_{p + 1} \oplus \dots \oplus a_n \oplus x \]

\[= (a_p \oplus a_{p + 1} \oplus \dots \oplus a_n \oplus sum_n) \oplus (x \oplus sum_n) \]

\[= sum_{p - 1} \oplus (x \oplus sum_n) \]

其中对于每一个位置的值 \(sum_{p - 1}\) 是无后效性的,我们只需要再在 \(x\) 上亦或一个 \(sum_n\) 就可以简单地用可持久化 01trie 维护了。

代码


const int mod = 998244353, maxn = 6 * 1e5 + 18;

const int limit = 1e7 + 5 * 1e6, maxk = 24;
class per_01trie {
private:
    int ch[limit][2], siz[limit], tot = 0;
    void push_up(int i) { siz[i] = siz[ls(i)] + siz[rs(i)]; }
    void copy(int i, int ii) {
        ls(i) = ls(ii), rs(i) = rs(ii);
        siz[i] = siz[ii];
    }
public:
    void build(int &rt) {
        rt = tot = 1;
        ls(rt) = rs(rt) = 0;
        siz[rt] = 0;
    }
    void insert(int &i, int ii, int x, int dep = maxk) {
        i = ++tot;
        copy(i, ii);
        if(dep < 0) { 
            siz[i]++;
            AC; 
        }
        int k = (x >> dep) & 1;
        insert(ch[i][k], ch[ii][k], x, dep - 1);
        push_up(i);
    }
    il int query(int i, int j, int x, int dep = maxk) {
        if(dep < 0) return 0;
        int k = ((x >> dep) & 1) ^ 1;
        if(siz[ch[i][k]] - siz[ch[j][k]]) return query(ch[i][k], ch[j][k], x, dep - 1) + (1ll << dep);
        else return query(ch[i][k ^ 1], ch[j][k ^ 1], x, dep - 1);
    }
} ds;

int n, m, a[maxn], rt[maxn];

void ACehomoxue() {
	cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    ds.build(rt[0]);
    for(int i = 1; i <= n; i++) {
        a[i] ^= a[i - 1];
        ds.insert(rt[i], rt[i - 1], a[i - 1]);
    }
    while(m--) {
        char opt;
        cin >> opt;
        if(opt == 'A') {
            n++;
            cin >> a[n];
            a[n] ^= a[n - 1];
            ds.insert(rt[n], rt[n - 1], a[n - 1]);
        } else {
            int l, r, x, ans;
            cin >> l >> r >> x;
            l--, x ^= a[n];
            ans = ds.query(rt[r], rt[l], x);
            cout << ans, el;
        }
    }
}

P4592 [TJOI2018] 异或

思路

两种方法。

第一种树剖套可持久化 01trie 十分显然,但是是 \(O(n \log^2n)\) 的。

第二种考虑开两个可持久化 01trie 分别维护两种操作。对于树上子树的询问,我们可以按照 dfs 序来建一颗树。对于树上路径,类比主席树题的 P2633 Count on a tree 即可。

不得不说可持久化 01trie 和主席树的技巧十分类似。

代码


const int mod = 998244353, maxn = 1e5 + 18;

const int limit = 1e7 + 18, maxk = 31;
class per_01trie {
private:
    int ch[limit][2], siz[limit], tot;
    void push_up(int i) { siz[i] = siz[ls(i)] + siz[rs(i)]; }
    void copy(int i, int ii) {
        siz[i] = siz[ii];
        ls(i) = ls(ii), rs(i) = rs(ii);
    }
public:
    void build(int &rt) {
        tot = rt = 1;
        ls(rt) = rs(rt) = 0;
        siz[rt] = 0;
    }
    void insert(int &i, int ii, int x, int dep = maxk) {
        i = ++tot;
        copy(i, ii);
        if(dep < 0) {
            siz[i]++;
            AC;
        }
        int k = (x >> dep) & 1;
        insert(ch[i][k], ch[ii][k], x, dep - 1);
        push_up(i);
    }
    il int query(int i, int j, int a, int b, int x, int dep = maxk) {
        if(dep < 0) AK;
        int k = ((x >> dep) & 1) ^ 1;
        if(siz[ch[i][k]] + siz[ch[j][k]] - siz[ch[a][k]] - siz[ch[b][k]]) return query(ch[i][k], ch[j][k], ch[a][k], ch[b][k], x, dep - 1) + (1ll << dep);
        else return query(ch[i][k ^ 1], ch[j][k ^ 1], ch[a][k ^ 1], ch[b][k ^ 1], x, dep - 1);
    }
} ds1, ds2; // ds1 为普通树上,ds2 为 dfn 序上。

int rt1[maxn], rt2[maxn];

int n, m, a[maxn];
vector <int> vec[maxn];

int dfn = 0, in[maxn], out[maxn], nxt[maxn][20], dep[maxn];
void dfs(int x, int fa) {
    debug(x);
    in[x] = ++dfn;
    dep[x] = dep[fa] + 1;
    nxt[x][0] = fa;
    ds1.insert(rt1[x], rt1[fa], a[x]);
    ds2.insert(rt2[dfn], rt2[dfn - 1], a[x]);
    for(int i = 1; i < 20; i++) nxt[x][i] = nxt[nxt[x][i - 1]][i - 1];
    for(auto to : vec[x]) {
        if(to == fa) continue;
        dfs(to, x);
    }
    out[x] = dfn;
}
il int lca(int u, int v) {
    if(dep[u] > dep[v]) swap(u, v);
    for(int i = 19; i >= 0; i--) if(dep[nxt[v][i]] >= dep[u]) v = nxt[v][i];
    if(u == v) return u;
    for(int i = 19; i >= 0; i--) if(nxt[v][i] != nxt[u][i]) v = nxt[v][i], u = nxt[u][i];
    return nxt[u][0]; 
}

void ACehomoxue() {
	cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int _ = 1, u, v; _ < n; _++) {
        cin >> u >> v;
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    ds1.build(rt1[0]);
    ds2.build(rt2[0]);
    dfs(1, 0);
    while(m--) {
        int op, u, v, x;
        cin >> op;
        if(op == 1) {
            cin >> u >> x;
            cout << ds2.query(rt2[out[u]], 0, rt2[in[u] - 1], 0, x);
        } else {
            cin >> u >> v >> x;
            int l = lca(u, v), fl;
            fl = nxt[l][0];
            cout << ds1.query(rt1[u], rt1[v], rt1[l], rt1[fl], x);
        }
        el;
    }
}
posted @ 2026-04-28 16:04  ACehomoxue  阅读(11)  评论(0)    收藏  举报