可持久化数据结构 (Persistent data structure) 总是可以保留每一个历史版本,并且支持操作的不可变特性 (immutable)。
主席树全称是可持久化权值线段树,给定 nn 个整数构成的序列 aa,将对于指定的闭区间 [l,r][l,r] 查询其区间内的第 kk 小值。
可持久化线段树#
变量#
#define mid ((l + r) >> 1)
int rot;
int rt[M];
struct node {
	int l, r, val;
} nod[M];
l, r: 左右孩子的指针;val: 权值;rot: 动态开点计数器;rt: 不同版本的根节点的编号。
过程#

每次修改操作修改的点的个数是一样的。
(例如上图,修改了 [1,8][1,8] 中对应权值为 11 的结点,红色的点即为更改的点)
只更改了 OlognOlogn 个结点,形成一条链,也就是说每次更改的结点数 == 树的高度。
主席树不能使用 x×2,x×2+1x×2,x×2+1 来表示左右儿子,而是应该动态开点,并保存每个节点的左右儿子编号。
在记录左右儿子的基础上,保存插入每个数的时候的根节点就可以实现持久化。
现在还有个问题,如何求 [l,r][l,r] 区间 kk 小值。
这里我们再联系另外一个知识:前缀和。
这个小东西巧妙运用了区间减法的性质,通过预处理从而达到 O1O1 回答每个询问。
我们可以发现,主席树统计的信息也满足这个性质。
如果需要得到 [l,r][l,r] 的统计信息,只需要用 [1,r][1,r] 的信息减去 [1,l−1][1,l−1] 的信息就行了。
关于空间问题,直接上个 25×10525×105(即 n << 5,大多数题目中空间限制都较为宽松,因此一般不用担心空间超限的问题)。
操作#
- 
建树
 
int build(int l, int r) {
	int u = ++ rot;
	if (l == r) {
		return u;
	}
	nod[u].l = build(l, mid);
	nod[u].r = build(mid + 1, r);
	return u;
}
- 
创建新节点
 
inline int newnod(int u) {
	++ rot;
	nod[rot] = nod[u];
	nod[rot].val = nod[u].val + 1;
	return rot;
}
修改时是在原来版本的基础上进行修改,先设置它们一样,由于插入了一个新的数,所以 nod[rot].val = nod[u].val + 1;。
- 
插入新节点
 
int add(int u, int l, int r, int pos) {
	u = newnod(u);
	if (l == r)	return u;
	if (pos <= mid) {
		nod[u].l = add(nod[u].l, l, mid, pos);
	}
	else {
		nod[u].r = add(nod[u].r, mid + 1, r, pos);
	}
	return u;
}
if (pos <= mid) {
	nod[u].l = add(nod[u].l, l, mid, pos);
}
else {
	nod[u].r = add(nod[u].r, mid + 1, r, pos);
}
修改时只会修改一条链,那也就意味着只会修改左孩子或右孩子中的一个,另一个保持不变。
- 
查询第 kk 大
 
int query(int l, int r, int lr, int rr, int k) {
	int x = nod[nod[rr].l].val - nod[nod[lr].l].val;
	if (l == r)	return l;
	if (k <= x) {
		return query(l, mid, nod[lr].l, nod[rr].l, k);
	}
	else {
		return query(mid + 1, r, nod[lr].r, nod[rr].r, k - x);
	}
}
int x = nod[nod[rr].l].val - nod[nod[lr].l].val;
这里利用了前缀和,求的是在 lrlr 到 rrrr 这个版本之间,左孩子的数量增加了多少,即 [lr,rr][lr,rr] 的前 xx 小的元素。
if (k <= x) {
	return query(l, mid, nod[lr].l, nod[rr].l, k);
}
else {
	return query(mid + 1, r, nod[lr].r, nod[rr].r, k - x);
}
如果 k<xk<x,那么说明第 kk 大的数在右孩子上,否则就在左子树上。
可持久化数组#
这个来源于洛谷的【模板】可持久化线段树 1(可持久化数组),需要支持修改操作,但没有了查询第 kk 大操作和插入操作。
变量#
#define mid ((l + r) >> 1)
int rot;
int rt[M];
struct node {
	int ls, rs, val;
} nod[(N << 5) + 10];
操作#
- 
创建新节点
 
inline int newnod(int u) { // 创建新节点
	++ rot;
	nod[rot] = nod[u];
	return rot;
}
- 
建树
 
int build(int l, int r) { // 建树
	int u = ++ rot;
	if (l == r) {
		scanf("%d", &nod[u].val);
		return u;
	}
	nod[u].ls = build(l, mid);
	nod[u].rs = build(mid + 1, r);
	return u;
}
- 
修改
 
int modify(int u, int l, int r, int pos, int c) { // 修改
	u = newnod(u);
	if (l == r) {
		nod[u].val = c;
	}
	else {
		if (pos <= mid) {
			nod[u].ls = modify(nod[u].ls, l, mid, pos, c);
		}
		else {
			nod[u].rs = modify(nod[u].rs, mid + 1, r, pos, c);
		}
	}
	return u;
}
- 
查询
 
int query(int u, int l, int r, int pos) { // 查询
	if (l == r) {
		return nod[u].val;
	}
	else {
		if (pos <= mid) {
			return query(nod[u].ls, l, mid, pos);
		}
		else {
			return query(nod[u].rs, mid + 1, r, pos);
		}
	}
}
模板#
namespace Persistent { // 可持久化数据结构
#define mid ((l + r) >> 1)
	
	const int N = 1e6 + 5;
	const int M = (N << 5) + 10;
	
	struct persistent_arr { // 可持久化数组
		int rot;
		int rt[M];
		
		struct node {
			int ls, rs, val;
		} nod[(N << 5) + 10];
		
		inline int newnod(int u) { // 创建新节点
			++ rot;
			nod[rot] = nod[u];
			return rot;
		}
		
		int build(int l, int r) { // 建树
			int u = ++ rot;
			if (l == r) {
				scanf("%d", &nod[u].val);
				return u;
			}
			nod[u].ls = build(l, mid);
			nod[u].rs = build(mid + 1, r);
			return u;
		}
		
		int modify(int u, int l, int r, int pos, int c) { // 修改
			u = newnod(u);
			if (l == r) {
				nod[u].val = c;
			}
			else {
				if (pos <= mid) {
					nod[u].ls = modify(nod[u].ls, l, mid, pos, c);
				}
				else {
					nod[u].rs = modify(nod[u].rs, mid + 1, r, pos, c);
				}
			}
			return u;
		}
		
		int query(int u, int l, int r, int pos) { // 查询
			if (l == r) {
				return nod[u].val;
			}
			else {
				if (pos <= mid) {
					return query(nod[u]