定义:可以访问历史版本的线段树为可持久化线段树
B站讲解视频:【AgOHの数据结构】主席树
可持久化线段树之所以可以访问历史版本,是因为宏观上看,它为每个版本维护了一棵树。当然,如果真的对每个版本建一颗树,时间空间复杂度都hold不住。所以建立i版本的树时,如果某颗子树相对于i-1版本没有变化,就可以直接使其父亲对应的指针指向i-1版本的这颗子树。
主席树更多的用于单点更新问题,可以为每一次更新后的版本建立一棵树,递归更新的过程中,先令当前节点等于上一个版本的节点,如果某个子树要更新,则新开一个空间存放更新的值。
洛谷模板题
题目大意: 给一个长度为n的数组,m次询问一个子区间的第K大。 n ≤ 2 e 5 , m ≤ 2 e 5 n \leq 2e5,m \leq 2e5 n≤2e5,m≤2e5
分析: 如果是一次询问整个数组的第K大,可以直接排序也可以通过借鉴快排达到平均 O ( n ) O(n) O(n)的复杂度。
如果有一颗权值线段树,每个节点维护权值在[l, r]范围内的数的个数,那么求[1, n]的第K大,可以通过递归求,如果左子树的数的个数为x, x大于等于K,递归查询左子树第K大,如果x小于等于K,递归查询右子树第K-x大。
为数组的每个前缀建立一颗权值线段树,查询[L, R]区间第K大时, 就可以通过树L-1, 树R结合起来查询第K大,在[l, mid]的数的个数即为R树对应个数减去L-1树对应个数。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200000 + 10;
struct node
{
int l, r, cnt;
node()
{
l = r = cnt = 0;
}
}nodes[maxn * 40];
//如果权值线段树某个节点的cnt为0,直接指向nodes[0],节约空间
//每次update新建logn个节点,注意nodes开够
int usecnt = 0;
int root[maxn];//各个版本的根节点
void update(int l, int r, int &rt, int pre, int v)
{
rt = ++usecnt;//分配新节点
nodes[rt] = nodes[pre];//赋值
nodes[rt].cnt++;//加入了一个新的数
if (l == r)return;
int mid = (l + r) >> 1;
//更新左边or右边,未更新的维持指向旧版本节点
if (v <= mid)update(l, mid, nodes[rt].l, nodes[pre].l, v);
else update(mid + 1, r, nodes[rt].r, nodes[pre].r, v);
}
int query(int l, int r, int k, int Ltree, int Rtree)
{
if (l == r)return l;
int mid = (l + r) >>