なんでバカのブログを読みたいの!为什么要看菜鸟的博客!|

园龄:粉丝:关注:

可持久化并查集 / 可持久化 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)\)

P3402 可持久化并查集 Code

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

仿照可持久化线段树的思路即可,如下面的例题。

P4735 最大异或和

区间求值用前缀和拆分。

\[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 中国大陆许可协议进行许可。

posted @   Garbage_fish  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起