可持久化并查集 / 可持久化 01 Trie 笔记
可持久化并查集
首先,可以把可持久化线段树当可持久化数组用,所以用一个可持久化数组来当父亲数组。
需要一种非均摊的路径压缩方法,考虑按秩合并,其中以树高为秩。
具体地,如果要合并 \(u\) 和 \(v\),让树高较小的一棵往树高较大的一棵合并,再更新树高即可。
如何更新树高?假设 \(u\) 是树高较大的一棵子树,若 \(h_u>h_v\),那么合并后树高不变,所以当且仅当 \(h_u=h_v\) 时,\(v\) 上面接了个 \(u\),所以 \(h_u\gets h_v+1\),即 \(h_u\gets h_u+1\),也可以用一个可持久化数组维护。
可以证明这样合并,可以使得单次查询的复杂度为 \(O(\log n)\)。
namespace Segtree { int root[N], idx; struct TREE { int ls, rs, f, dep; } t[N]; void build(int &rt, int l, int r) { rt = ++idx; if (l == r) { t[rt].f = l; return; } int mid = l + r >> 1; build(t[rt].ls, l, mid); build(t[rt].rs, mid + 1, r); } void update_f(int &rt, int l, int r, int x, int v) { t[++idx] = t[rt]; rt = idx; if (l == r) { t[rt].f = v; return; } int mid = l + r >> 1; x <= mid ? update_f(t[rt].ls, l, mid, x, v) : update_f(t[rt].rs, mid + 1, r, x, v); } void update_dep(int &rt, int l, int r, int x) { t[++idx] = t[rt]; rt = idx; if (l == r) { t[rt].dep++; return; } int mid = l + r >> 1; x <= mid ? update_dep(t[rt].ls, l, mid, x) : update_dep(t[rt].rs, mid + 1, r, x); } TREE query(int rt, int l, int r, int x) { if (l == r) return t[rt]; int mid = l + r >> 1; return x <= mid ? query(t[rt].ls, l, mid, x) : query(t[rt].rs, mid + 1, r, x); } } using namespace Segtree; namespace UDS { TREE gf(int x, int ver) { TREE f = query(root[ver], 1, n, x); return f.f == x ? f : gf(f.f, ver); } void un(int x, int y, int ver) { TREE fx = gf(x, ver), fy = gf(y, ver); if (fx.f == fy.f) return; if (fx.dep < fy.dep) swap(fx, fy); update_f(root[ver], 1, n, fy.f, fx.f); if (fx.dep == fy.dep) update_dep(root[ver], 1, n, fx.f); } } using namespace UDS;
可持久化 01 Trie
仿照可持久化线段树的思路即可,如下面的例题。
区间求值用前缀和拆分。
\[s_i=\operatorname{xor}_{j=1}^i a_i
\]
\[\max_{p\in [l,r]}((\operatorname{xor}_{i=p}^n a_i)\operatorname{xor} x)=\max_{p\in [l,r]}(s_{p-1}\operatorname{xor} (s_n \operatorname{xor} x))
\]
暴力的想法是对于每个 \(p\) 建一棵 01 Trie,但是发现可以用可持久化的思想,“复用”以前的节点。对于区间查询,打一个时间戳,标记某个树上的节点是否在查询的区间内即可。
对于本模板题,异或 \(0\) 是合法的,所以还需要提前插入一个 \(0\)。
namespace TRIE { int root[N], t[N][2], tim[N], idx; void insert(int u, int v, int x, int step) { if (step < 0) return; int c = x >> step & 1; t[u][c ^ 1] = t[v][c ^ 1]; t[u][c] = ++idx; tim[t[u][c]] = tim[t[v][c]] + 1; insert(t[u][c], t[v][c], x, step - 1); } int query(int l, int r, int x, int step) { if (step < 0) return 0; int c = x >> step & 1; if (tim[t[r][c ^ 1]] > tim[t[l][c ^ 1]]) return 1 << step | query(t[l][c ^ 1], t[r][c ^ 1], x, step - 1); else return query(t[l][c], t[r][c], x, step - 1); } } using namespace TRIE; signed main() { IOS; cin >> n >> m; root[0] = ++idx; insert(root[0], 0, 0, 25); for (int i = 1, x; i <= n; i++) { cin >> x; q[i] = (q[i - 1] ^ x); root[i] = ++idx; insert(root[i], root[i - 1], q[i], 25); } for (int i = 1, l, r, x; i <= m; i++) { char c; cin >> c; if (c == 'A') { cin >> x; n++; q[n] = (q[n - 1] ^ x); root[n] = ++idx; insert(root[n], root[n - 1], q[n], 25); } else { cin >> l >> r >> x; l--, r--; if (!l) cout << query(0, root[r], q[n] ^ x, 25) << "\n"; else cout << query(root[l - 1], root[r], q[n] ^ x, 25) << "\n"; } } return 0; }
本文作者:Garbage fish's Blog
本文链接:https://www.cnblogs.com/Garbage-fish/p/18730290
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步