关于可持久化并查集


考虑并查集的过程,\(fa_x=y\) 仿佛是不可逆的。你考虑这是一个赋值,且每次操作只会实行一次(usually),发现这可以可持久化。于是用主席树维护这个 \(fa\) 即可。
但由于你直接跳 \(fa\)\(\mathcal {O}(n)\),又不能改变 \(fa\) 的形态,于是只能用按秩合并,然后查根是 \(\mathcal{O}(log_2^2 n)\) 的。板子:

#include 
#include 
#include 
#include 
#include 
#define ls tree[p].L
#define rs tree[p].R
using namespace std;
const int MAXN = 2e5 + 5;
struct Node {
	int L, R;
}tree[MAXN * 25];
int n, m, now, root[MAXN], dep[MAXN * 25], fa[MAXN * 25], tot;
int ask(int p, int l, int r, int x) { // 查询位置 x在时态 t下对应线段树的编号 
	if(l == r) return p;
	int mid = (l + r) >> 1; return x <= mid ? ask(ls, l, mid, x) : ask(rs, mid + 1, r, x);
}
void build(int &p, int l, int r) { // 建好树,弄好 dep,fa 
	p = ++ tot;
	if(l == r) { dep[p] = 1; fa[p] = l; return; }
	int mid = (l + r) >> 1; build(ls, l, mid); build(rs, mid + 1, r);
}
int fnd(int x) { int p = ask(root[now], 1, n, x); return fa[p] ^ x ? fnd(fa[p]) : x; } // x 为实际编号, p 为 smt对应的编号 
void add(int &p, int q, int l, int r, int x, int y, int f) {
	if(!f) p = ++ tot, tree[p] = tree[q], dep[p] = dep[q], fa[p] = fa[q]; // 注意 dep,fa 也要搞过来 
	// 另外加上 !f 卡常(基本少建 1/2的点) 
	if(l == r) {
		if(!f) fa[p] = y;
		else dep[p] = y;
		return;
	}
	int mid = (l + r) >> 1;
	if(x <= mid) add(ls, tree[q].L, l, mid, x, y, f);
	else add(rs, tree[q].R, mid + 1, r, x, y, f);
}
void makefa(int u, int v, int p, int q) { // u,v 下标 p,q 对应编号 
	int r = dep[p] == dep[q] ? dep[q] + 1 : dep[q];
	now ++; add(root[now], root[now - 1], 1, n, u, v, 0); add(root[now], root[now], 1, n, v, r, 1);
}
void mrg(int x, int y) {
	int u = fnd(x), v = fnd(y);
	if(u == v) { now ++; root[now] = root[now - 1]; return; } // 注意这里 root 也要搞过来 
	int p = ask(root[now], 1, n, u), q = ask(root[now], 1, n, v);
	if(dep[p] <= dep[q]) makefa(u, v, p, q);
	else makefa(v, u, q, p);
}
int main() {
	int f, l, r, x; scanf("%d%d", &n, &m); build(root[0], 1, n);
	for(int i = 1; i <= m; i ++) {
		scanf("%d", &f);
		if(f == 1) scanf("%d%d", &l, &r), mrg(l, r);
		else if(f == 2) scanf("%d", &x), root[++ now] = root[x];
		else now ++, root[now] = root[now - 1], scanf("%d%d", &l, &r), printf("%d\n", fnd(l) == fnd(r));
	}
	return 0;
} 

好,既然你已经学会了可持久化并查集了,让我们来看一道 例题(NOI2018 归程) 吧。
考虑如果可以连离线,我们可以按照询问的水位高度排序,顺序处理,维护一个并查集即可。
那这个在线,肯定就想到可持久化并查集,离散化后把水位作为时间即可。当然并查集的时候要有权值的更新,具体地也不难搞。
然后这里有一个卡常技巧是对于一个时间,新建的点只用建一次,剩下的直接更新即可。具体看代码。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define mr make_pair
#define pr pair 
#define ls tree[p].L
#define rs tree[p].R
using namespace std;
const int MAXN = 4e5 + 5;
struct Smt {
	int L, R;
}tree[MAXN * 45];
int n, m, lsh[MAXN], tot, cnt, a[MAXN], dp[MAXN], X[MAXN], Y[MAXN];
int root[MAXN], now, dep[MAXN * 45], fa[MAXN * 45], val[MAXN * 45]; // askroom
bool vis[MAXN];
pr x, q;
vector  edge[MAXN];
vector  all[MAXN];
priority_queue , greater  > que;
int ask(int p, int l, int r, int x) {
	if(l == r) return p;
	int mid = (l + r) >> 1; return x <= mid ? ask(ls, l, mid, x) : ask(rs, mid + 1, r, x);
}
void build(int &p, int l, int r) {
	p = ++ tot; ls = rs = 0;
	if(l == r) { dep[p] = 1; fa[p] = l; val[p] = dp[l]; return; }
	int mid = (l + r) >> 1; build(ls, l, mid); build(rs, mid + 1, r);
}
int fnd(int x) { int p = ask(root[now], 1, n, x); return fa[p] ^ x ? fnd(fa[p]) : x; }
void add(int &p, int q, int l, int r, int x, int y, int f) {
	if(f <= 1) p = ++ tot, tree[p] = tree[q], dep[p] = dep[q], fa[p] = fa[q], val[p] = val[q];
	if(l == r) {
		if(f == 0) fa[p] = y;
		else if(f == 1) dep[p] = y;
		else val[p] = min(val[p], y);
		return;
	}
	int mid = (l + r) >> 1;
	if(x <= mid) add(ls, tree[q].L, l, mid, x, y, f);
	else add(rs, tree[q].R, mid + 1, r, x, y, f);
}
void makefa(int u, int v, int p, int q) {
	int r = dep[p] == dep[q] ? dep[q] + 1 : dep[q];
	add(root[now], root[now], 1, n, u, v, 0); add(root[now], root[now], 1, n, v, r, 1); add(root[now], root[now], 1, n, v, val[p], 2);
}
void mrg(int x, int y) {
	int u = fnd(x), v = fnd(y);
	if(u == v) return;
	int p = ask(root[now], 1, n, u), q = ask(root[now], 1, n, v);
	if(dep[p] <= dep[q]) makefa(u, v, p, q);
	else makefa(v, u, q, p);
}
void Dijkstra() {
	for(int i = 1; i <= n; i ++) dp[i] = 2e9 + 1, vis[i] = 0;
	dp[1] = 0; vis[1] = 1; que.push(mr(0, 1));
	while(!que.empty()) {
		x = que.top(); que.pop(); vis[x.second] = 1;
		for(auto y : edge[x.second]) { // rev spfa
			if(!vis[y.second] && dp[y.second] > dp[x.second] + y.first) {
				dp[y.second] = dp[x.second] + y.first; que.push(mr(dp[y.second], y.second));
			}
		}
	}
}
void write(int x) {
	if(x <= 9) { putchar(x + '0'); return; }
	write(x / 10); putchar(x % 10 + '0');
}
void read(int &x) {
	x = 0; bool f = 1; char c = getchar();
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = 0;
	for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
	x = f ? x : -x;
}
int main() {
//	freopen("return.in", "r", stdin);
  //  freopen("return.out", "w", stdout);
	int T, u, v, x, y;
	for(scanf("%d", &T); T; T --) {
		read(n); read(m); tot = 0; cnt = 0;
		for(int i = 1; i <= n; i ++) edge[i].clear();
		for(int i = 1; i <= m; i ++) all[i].clear();
		for(int i = 1; i <= m; i ++) {
			read(u); read(v); read(x); edge[u].push_back(mr(x, v)); X[i] = u; Y[i] = v;
			edge[v].push_back(mr(x, u)); read(a[i]); lsh[++ cnt] = a[i];
		}
		sort(lsh + 1, lsh + 1 + cnt); tot = unique(lsh + 1, lsh + 1 + cnt) - lsh - 1;
		for(int i = 1; i <= m; i ++) a[i] = lower_bound(lsh + 1, lsh + 1 + cnt, a[i]) - lsh, all[a[i]].push_back(i);
		Dijkstra(); build(root[cnt + 1], 1, n);
		int Q, K, S, las = 0; read(Q); read(K); read(S);
		for(int i = cnt; i >= 1; i --) {
			root[i] = root[i + 1]; now = i;
			for(auto y : all[i]) mrg(X[y], Y[y]);
		}
		for(int i = 1; i <= Q; i ++) {
			read(x); read(y); x = (x + K * las - 1) % n + 1; y = (y + K * las) % (S + 1);
			int t = upper_bound(lsh + 1, lsh + 1 + cnt, y) - lsh; now = t;
			write(las = val[ask(root[now], 1, n, fnd(x))]); putchar('\n');
		}
	}
	return 0;
}

相关