可持久化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\) 我们注意到:
其中对于每一个位置的值 \(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;
}
}

浙公网安备 33010602011771号