可持久化数据结构
可持久化数据结构大同小异,其核心就在于修改的操作变成加点。
可持久化线段树
引子:静态区间第 $k$ 小。
较为朴素的方法是,开 $n$ 个权值线段树维护前 $i$ 个数,二分“小于等于 $\text{mid}$ 的个数”,然后用第 $r$ 个线段树的值减第 $l-1$ 个的判断。
然后这里面有很多冗余状态,实际上每次插入只会影响一条链,那么就新建一条这样的链,注意链要指向原树(其实是第 $i-1$ 的树)。
值得注意的是,这个二分其实也是不需要的,可以在线段树上跑:像 BST 那样,左边可以就一起往左边,不然就往右走。
const int N = 100010;
struct Seg
{
	int lt[N<<5], rt[N<<5], cnt;
	ll sum[N<<5];
	void build(int &x, int l, int r)
	{
		x = ++cnt;
		if(l == r) return;
		int mid = (l + r) >> 1;
		build(lt[x], l, mid);
		build(rt[x], mid + 1, r);
	}
	int change(int x, int l, int r, int p)
	{
		int Newx = ++cnt; lt[Newx] = lt[x], rt[Newx] = rt[x], sum[Newx] = sum[x] + 1;
		if (l == r) return Newx;
		int mid = (l + r) >> 1;
		if(p <= mid) lt[Newx] = change(lt[Newx], l, mid, p);
		else rt[Newx] = change (rt[Newx], mid + 1, r, p);
		return Newx;
	}
	int query (int L, int R, int l, int r, int p)
	{
		int ans, mid = (l + r) >> 1, x = sum[lt[R]] - sum[lt[L]];
		if(l == r) return l;
		if(p <= x) ans = query (lt[L], lt[R], l, mid, p);
		else ans = query (rt[L], rt[R], mid + 1, r, p - x);
		return ans;
	}
}sgt;
int n, m;
ll a[N], b[N]; int root[N];
int main()
{
	scanf ("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf ("%d", &a[i]), b[i] = a[i];
	sort(b + 1, b + 1 + n);
	int nn = unique(b + 1, b + 1 + n) - b - 1;
	sgt.build(root[0], 1, nn);
	for (int i = 1; i <= n; i++)
	{
		int p = lower_bound(b + 1, b + 1 + nn, a[i]) - b;
		root[i] = sgt.change(root[i - 1], 1, nn, p);
	}
	for (int i = 1; i <= m; i++)
	{
		int l, r, k;
		scanf ("%d%d%d", &l, &r, &k);
		int ans = sgt.query(root[l - 1], root[r], 1, nn, k);
		printf("%lld\n", b[ans]);
	}
	return 0;
} 
可持久化数组
比较基础的,也发挥了其基础的作用:很多都依赖它。
功能就是改某个历史版本中某位的值,访问某位置某个历史版本的值。
用可持久化线段树维护。
const int N = 1e6 + 10;
int n, m, a[N], root[N << 4];
inline ll read()
{
    ll f = 1, x = 0;
	char ch;
    do{ ch = getchar(); if(ch == '-') f = -1;} while(ch < '0' || ch > '9');
    do{ x = x * 10 + ch - '0'; ch = getchar();} while(ch >= '0' && ch <= '9');
    return f * x;
}
struct Seg
{
	int lt[N << 4], rt[N << 4], cnt;
	ll val[N << 4];
	void build (int &x, int l, int r)
	{
		x = ++cnt;
		if (l == r) {val[x] = a[l]; return ;}
		int mid = l + r >> 1;
		build (lt[x], l, mid);
		build (rt[x], mid + 1, r); 
	}
	int change (int x, int l, int r, int p, ll v)
	{
		int Newx = ++ cnt; lt[Newx] = lt[x], rt[Newx] = rt[x], val[Newx] = val[x];
		if (l == r) {val[Newx] = v; return Newx;}
		int mid = l + r >> 1;
		if (p <= mid) lt[Newx] = change (lt[x], l, mid, p, v);
		else rt[Newx] = change (rt[x], mid + 1, r, p, v);
		return Newx;
	}
	
	int query (int x, int l, int r, int p)
	{
		if (l == r) {return val[x];}
		int mid = l + r >> 1;
		if (p <= mid) return query(lt[x], l, mid, p);
		else return query(rt[x], mid + 1, r, p);
	}
	
}T;
int main()
{
	n = read(), m = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	T.build(root[0], 1, n);
	for (int pre, opt, x, i = 1; i <= m; i++)
	{
		pre = read(), opt = read(), x = read();
		if (opt == 1) {root[i] = T.change(root[pre], 1, n, x, read());} 
		else {printf ("%d\n", T.query(root[i] = root[pre], 1, n, x));}
	}
	return 0;
}
可持久化并查集
朴素的并查集就是单纯的数组操作。路径压缩不好维护,考虑按秩合并:按树高合并。
const int N = 1e5 + 5;
inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}
namespace Main {
	int n, m;
	struct node {
		int l, r, dep, fa;
	} t[N * 50];
	int tot, rt[N << 1];
	void build (int &p, int l, int r) {
		p = ++tot;
		if (l == r) { t[p].fa = l; return ;}
		int mid = (l + r) >> 1;
		build (t[p].l, l, mid);
		build (t[p].r, mid + 1, r);
	}
	void add (int &p, int q, int l, int r, int x) {
		p = ++tot; t[p] = t[q];
		if (l == r) { t[p].dep++; return; }
		int mid = (l + r) >> 1;
		if (x <= mid) add (t[p].l, t[q].l, l, mid, x);
		else add (t[p].r, t[q].r, mid + 1, r, x);
	}
	int query (int p, int l, int r, int x) {
		if (l == r) return p;
		int mid = (l + r) >> 1;
		if (x <= mid) return query (t[p].l, l, mid, x);
		else return query (t[p].r, mid + 1, r, x);
	}
	void Merge (int &p, int q, int l, int r, int x, int fa) {
		p = ++tot;
		t[p].l = t[q].l, t[p].r = t[q].r;
		if (l == r) { t[p].dep = t[q].dep; t[p].fa = fa; return; }
		int mid = (l + r) >> 1;
		if (x <= mid) Merge (t[p].l, t[q].l, l, mid, x, fa);
		else Merge (t[p].r, t[q].r, mid + 1, r, x, fa);
	}
	int find (int p, int x) {
		int now = query (p, 1, n, x);
		if (t[now].fa == x) return now;
		else return find(p, t[now].fa);
	}
	void merge (int ver, int x, int y) {
		rt[ver] = rt[ver - 1];
		int fx = find(rt[ver], x), fy = find(rt[ver], y);
		if (t[fx].fa != t[fy].fa) {
			if (t[fx].dep > t[fy].dep) swap(fx, fy);
			Merge(rt[ver], rt[ver - 1], 1, n, t[fx].fa, t[fy].fa);
			if (t[fx].dep == t[fy].dep) add (rt[ver], rt[ver], 1, n, t[fy].fa);
		}
	}
	bool Find(int ver, int x, int y) {
		rt[ver] = rt[ver - 1];
		int fx = find(rt[ver], x);
		int fy = find(rt[ver], y);
		return t[fx].fa == t[fy].fa;
	}
	int main () {
		n = Read(), m = Read();
		build(rt[0], 1, n);
		for (int i = 1; i <= m; i++) {
			int opt = Read();
			if (opt == 1) { int a = Read(), b = Read(); merge(i, a, b); }
			if (opt == 2) { int k = Read(); rt[i] = rt[k]; }
			if (opt == 3) { int a = Read(), b = Read(); printf ("%d\n", Find(i, a, b)); }
		}
		return 0;
	}
}
int main () {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	Main::main();
	return 0;
}
剩下的来不及写了。
