主席树
主席树
这个也叫可持久化线段树。
基本思想
所谓可持久化,就是可以保留每一个历史版本。对于线段树而言,可持久化就意味着可以保留多个历史版本的线段树。
首先,直接储存 \(n\) 棵线段树显然会爆空间。
我们考虑当我们进行一次单点修改的时候,会收到影响的节点只有原树上的一条根链会收到影响。也就是说,我们储存一个新的版本时,无需新建一整棵树,只需要新建一条链即可,剩下的直接连到原来的节点上即可。

然后就是一个类似于动态开店线段树的东西去写就行了。
现在来考虑区间修改。我们其实这个东西是复用节点,所以是无法进行下放懒标记的操作的。那么我们使用标记永久化即可。
例题
单点修改历史版本,查询历史版本单点查询。
直接上朴素的可持久化线段树即可。
#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;
}
给定序列,查询区间第 \(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;
}

浙公网安备 33010602011771号