【数据结构】可持久化并查集
事实上是并查集套一个可持久化数组。
int a[100005];
struct SegmentTree {
#define ls lch[o]
#define rs rch[o]
static const int MAXN = 1e5 + 10;
static const int MAXM = 2e5 + 10;
int n;
int top;
int lch[MAXN * 2 + MAXM * 18];
int rch[MAXN * 2 + MAXM * 18];
int sum[MAXN * 2 + MAXM * 18];
int ver[MAXM], cur;
int Clone(int o) {
++top;
sum[top] = sum[o];
lch[top] = ls;
rch[top] = rs;
return top;
}
void PushUp(int o) {
sum[o] = sum[ls] + sum[rs];
}
int BuildHelp(int l, int r) {
int o = Clone(0);
if(l == r) {
sum[o] = a[l];
ls = 0;
rs = 0;
return o;
}
int m = (l + r) >> 1;
ls = BuildHelp(l, m);
rs = BuildHelp(m + 1, r);
PushUp(o);
return o;
}
int AddHelp(int o, int l, int r, int p, ll v) {
o = Clone(o);
if(l == r) {
sum[o] += v;
return o;
}
int m = (l + r) >> 1;
if(p <= m)
ls = AddHelp(ls, l, m, p, v);
if(p >= m + 1)
rs = AddHelp(rs, m + 1, r, p, v);
PushUp(o);
return o;
}
int SetHelp(int o, int l, int r, int p, ll v) {
o = Clone(o);
if(l == r) {
sum[o] = v;
return o;
}
int m = (l + r) >> 1;
if(p <= m)
ls = SetHelp(ls, l, m, p, v);
if(p >= m + 1)
rs = SetHelp(rs, m + 1, r, p, v);
PushUp(o);
return o;
}
int SumHelp(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr)
return sum[o];
int m = (l + r) >> 1;
int res = 0;
if(ql <= m)
res = res + SumHelp(ls, l, m, ql, qr);
if(qr >= m + 1)
res = res + SumHelp(rs, m + 1, r, ql, qr);
return res;
}
void Build(int _n) {
n = _n;
cur = 0;
top = 0;
ver[++cur] = BuildHelp(1, n);
}
void Add(int id, int pos, int val) {
ver[++cur] = AddHelp(ver[id], 1, n, pos, val);
}
void Set(int id, int pos, int val) {
ver[++cur] = SetHelp(ver[id], 1, n, pos, val);
}
int Sum(int id, int lpos, int rpos) {
return SumHelp(ver[id], 1, n, lpos, rpos);
}
};
struct DisjointSet {
int n;
SegmentTree find;
SegmentTree size;
int ver[200005], cur;
void Init(int n) {
cur = 0;
for(int i = 1; i <= n; i++)
a[i] = i;
find.Build(n);
for(int i = 1; i <= n; i++)
a[i] = 1;
size.Build(n);
ver[++cur] = find.cur;
}
int Find(int id, int x) {
int fx = find.Sum(ver[id], x, x);
if(fx == x)
return x;
return Find(id, fx);
}
int Size(int id, int x) {
x = Find(id, x);
return size.Sum(id, x, x);
}
int Merge(int id, int x, int y) {
x = Find(id, x);
y = Find(id, y);
if(x == y) {
ver[++cur] = ver[id];
return 0;
}
int sx = size.Sum(ver[id], x, x);
int sy = size.Sum(ver[id], y, y);
if(sx < sy) {
swap(x, y);
swap(sx, sy);
}
find.Set(ver[id], y, x);
size.Add(ver[id], x, sy);
ver[++cur] = find.cur;
return x;
}
} ds;
这里默认merge操作产生一个新的版本。并查集的ver[u]表示并查集的版本u对应线段树的版本ver[u]
也就是说,并查集的ver[u]对应的线段树根为find.ver[ver[u]],这里find和size的树根始终处于同一版本号,同步变化。
注意分清楚哪些操作产生新的版本。
假如是验证中的例题,跳转操作也产生新的版本,其他操作都在并查集的最后一个版本上修改(和可持久化数组的每个修改都带old版本不同)。
注意并查集的最后一个版本不见得对应线段树的最后一个版本,因为这里为了节省空间使得线段树并不会因为Sum操作产生版本。