主席树

主席树

这个也叫可持久化线段树。

基本思想

所谓可持久化,就是可以保留每一个历史版本。对于线段树而言,可持久化就意味着可以保留多个历史版本的线段树。

首先,直接储存 \(n\) 棵线段树显然会爆空间。

我们考虑当我们进行一次单点修改的时候,会收到影响的节点只有原树上的一条根链会收到影响。也就是说,我们储存一个新的版本时,无需新建一整棵树,只需要新建一条链即可,剩下的直接连到原来的节点上即可。

然后就是一个类似于动态开店线段树的东西去写就行了。

现在来考虑区间修改。我们其实这个东西是复用节点,所以是无法进行下放懒标记的操作的。那么我们使用标记永久化即可。

例题

P3919 【模板】可持久化线段树 1(可持久化数组)

单点修改历史版本,查询历史版本单点查询。

直接上朴素的可持久化线段树即可。

#include <bits/stdc++.h>
#define il inline

using namespace std;

const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
	int x = 0; char ch = getchar(); bool t = 0;
	while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return t ? -x : x;
}
const int N = 1e6 + 10;
int n, m, a[N];
struct node {
	int l, r, s;
} tree[N << 5];
#define lc tree[p].l
#define rc tree[p].r
int root[N], tot;
il void build(int &p, int l, int r) {
	if (!p) p = ++tot;
	if (l == r) {
		tree[p].s = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(lc, l, mid), build(rc, mid + 1, r);
}
il int update(int p, int l, int r, int x, int v) {
	int rt = ++tot;
	tree[rt] = tree[p];
	if (l == r) {
		tree[rt].s = v;
		return rt;
	}
	int mid = l + r >> 1;
	if (x <= mid) tree[rt].l = update(lc, l, mid, x, v);
	else tree[rt].r = update(rc, mid + 1, r, x, v);
	return rt;
}
il int query(int p, int l, int r, int x) {
	if (l == r) return tree[p].s;
	int mid = l + r >> 1;
	if (x <= mid) return query(lc, l, mid, x);
	else return query(rc, mid + 1, r, x);
}
int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
	}
	int tot = 0;
	build(root[tot], 1, n);
	while (m--) {
		int v = read(), op = read(), p = read();
		tot++;
		if (op == 1) {
			int c = read();
			root[tot] = update(root[v], 1, n, p, c);
		} else {
			root[tot] = root[v];
			printf("%d\n", query(root[v], 1, n, p));
		}
	}
	
	return 0;
}

P3834 【模板】可持久化线段树 2

给定序列,查询区间第 \(k\) 小。

求静态区间第 \(k\) 小是主席树经典的操作之一。

首先考虑全局第 \(k\) 小如何查询。在权值线段树上二分即可。

现在要求 \([l,r]\) 的第 \(k\) 小,实际上只需要知道 \([l,r]\) 内的数构成的权值线段树上的信息即可。考虑运用前缀和的思想。我们建立一棵主席树,每插入序列中一个元素就储存一个版本。那么第 \(p\) 个版本的线段树储存的就是 \([1,p]\) 的元素的权值线段树。现在要求 \([l,r]\) 的信息,用 \([1,r]\) 的信息减去 \([1,l-1]\) 的信息即可。

#include <bits/stdc++.h>
#define il inline

using namespace std;

const int bufsz = 1 << 20;
char ibuf[bufsz], *p1 = ibuf, *p2 = ibuf;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, bufsz, stdin), p1 == p2) ? EOF : *p1++)
il int read() {
	int x = 0; char ch = getchar(); bool t = 0;
	while (ch < '0' || ch > '9') {t ^= ch == '-'; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
	return t ? -x : x;
}
const int N = 2e5 + 10;
int n, m, a[N], b[N];
struct node {
	int l, r, s;
} tree[N << 5];
#define lc tree[p].l
#define rc tree[p].r
int root[N], tot;
il void build(int &p, int l, int r) {
	if (!p) p = ++tot;
	if (l == r) return;
	int mid = l + r >> 1;
	build(lc, l, mid), build(rc, mid + 1, r);
}
il void pushup(int p) {
	tree[p].s = tree[lc].s + tree[rc].s;
}
il int update(int p, int l, int r, int x, int v) {
	int rt = ++tot;
	tree[rt] = tree[p];
	if (l == r) {
		tree[rt].s += v;
		return rt;
	}
	int mid = l + r >> 1;
	if (x <= mid) tree[rt].l = update(lc, l, mid, x, v);
	else tree[rt].r = update(rc, mid + 1, r, x, v);
	pushup(rt);
	return rt;
}
il int kth(int p, int q, int l, int r, int k) {
	if (l == r) return l;
	int mid = l + r >> 1;
	int sum = tree[tree[q].l].s - tree[tree[p].l].s;
	if (k <= sum) {
		return kth(tree[p].l, tree[q].l, l, mid, k);
	} else {
		return kth(tree[p].r, tree[q].r, mid + 1, r, k - sum);
	}
}
int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
		b[i] = a[i];
	}
	sort(b + 1, b + 1 + n);
	int ll = unique(b + 1, b + 1 + n) - b - 1;
	build(root[0], 1, n);
	for (int i = 1; i <= n; i++) {
		a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;
		root[i] = update(root[i - 1], 1, n, a[i], 1);
	}
	while (m--) {
		int l = read(), r = read(), k = read();
		printf("%d\n", b[kth(root[l - 1], root[r], 1, n, k)]);
	}
	
	return 0;
}
posted @ 2025-08-13 21:00  Zctf1088  阅读(9)  评论(0)    收藏  举报