可持久化线段树

用途:第\(k\)大/小问题(最经典的)

【示例】第K小问题

洛谷P3864

题目大意

给定一组序列\(a_1,a_2,a_3,...,a_n\),同时给定\(m\)组查询,包括\(l,r,k\),求\([l,r]\)区间中的第\(k\)小的数

解题思路

正常的,会先考虑直接加点,排序,但这样子写显然会超时

而后,想起线段树,线段树能够处理区间的最小值

那么,如何转换为第\(k\)小?

我们可以对每一个\(i\),建立一个线段树,将其值插入到对应的节点中(实际上这更像是一个桶)

这样对于区间\([1,i]\),其第\(k\)小就是好求的,正常查询即可

而我们只要运用前缀和的思想,自然就可以求出区间\([l,r]\)

但是很显然,这样子写空间不够用,考虑优化

使用可持久化线段树

可持久化线段树能够做到减少点的数量,其思路如下:

因为对于每一次加点,实际上修改的值是很少的,不超过\(logn\),那么,就可以只记录那些被修改的点,至于没有被修改的点,则可以直接继承前面的值

这样子,我们就可以建出很多棵“残缺”的线段树,但是这对答案没有影响

细节问题

1.需要开一个\(root[]\)数组,定位每一棵线段树

2.重复元素仍需要新建线段树,因此线段树的个数一定为\(n\)

示例代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
struct node{
    int L,R;
    int sum;
}tre[N << 5];
int a[N],root[N];
int b[N];
int n,m;
int cnt;
int update(int pre,int pl,int pr,int x){
    int rt = ++cnt;
    tre[rt] = tre[pre];
    tre[rt].sum ++ ;
    int mid = (pl + pr) >> 1;
    if(pl < pr){
        if(x <= mid) tre[rt].L = update(tre[pre].L,pl,mid,x);
        else tre[rt].R = update(tre[pre].R,mid+1,pr,x);
    }
    return rt;
}
int query(int u,int v,int pl,int pr,int k){
    if(pl == pr) return pl;
    int x = tre[tre[v].L].sum - tre[tre[u].L].sum;
    int mid = (pl + pr) >> 1;
    if(x >= k) return query(tre[u].L,tre[v].L,pl,mid,k);
    else return query(tre[u].R,tre[v].R,mid+1,pr,k - x);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        b[i] = a[i];
    }
    sort(b+1,b+1+n);
    int len = unique(b+1,b+1+n) - b - 1;
    for(int i=1;i<=n;i++){
        int x = lower_bound(b+1,b+1+len,a[i]) - b;
        root[i] = update(root[i-1],1,len,x);
    }
    for(int i=1;i<=m;i++){
        int l,r,k;
        cin >> l >> r >> k;
        int t = query(root[l-1],root[r],1,len,k);
        cout << b[t] << '\n';
    }
    return 0;
}

【示例】支持版本修改/查询的线段树

洛谷P3919

题目大意

要求对一个数组,能够修改其历史版本并查询

解题思路

参照之前的可持久化线段树策略,考虑对每一个版本的线段树,都新建一棵线段树(自然不是完全新建,而是跟前面一样,只关注修改的,对修改的部分加点),之后就是板子了……

示例代码

#include<bits/stdc++.h>
using namespace std;
#define fstios ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
const int N = 1e6 + 10;
struct node{
	int L,R;
	int val;
}tre[N << 5];
int root[N],a[N];
int cnt;
int n,m;
int build(int x,int pl,int pr){
	int rt = ++cnt;
	if(pl == pr){
		tre[rt].val = a[pl];
		return rt;
	}
	int mid = (pl + pr) >> 1;
	tre[rt].L = build(tre[rt].L,pl,mid);
	tre[rt].R = build(tre[rt].R,mid+1,pr);
	return rt;
}
int update(int pre,int pl,int pr,int x,int k){
	int rt = ++ cnt;
	tre[rt] = tre[pre];
	if(pl == pr){
		tre[rt].val = k;
		return rt;
	}
	int mid = (pl + pr ) >> 1;
	if(x <= mid) tre[rt].L = update(tre[pre].L,pl,mid,x,k);
	else tre[rt].R = update(tre[pre].R,mid+1,pr,x,k);
	return rt;
}
int query(int pre,int pl,int pr,int x){
	if(pl == pr){
		return tre[pre].val;
	}
	int mid = (pl + pr) >> 1;
	if(x <= mid) return query(tre[pre].L,pl,mid,x);
	else return query(tre[pre].R,mid+1,pr,x);
}
int main()
{
	fstios;
	cin >> n >> m;
	for(int i=1;i<=n;i++)
		cin >> a[i];
	root[0] = build(0,1,n);
	for(int i=1;i<=m;i++){
		int v;
		cin >> v;
		int op;
		cin >> op;
		if(op == 1){
			int p,c;
			cin >> p >> c;
			root[i] = update(root[v],1,n,p,c);
		}
		else{
			int p;
			cin >> p;
			root[i] = root[v];
			cout << query(root[v],1,n,p) << "\n";
		}
	}
	return 0;
}
posted @ 2025-07-17 15:21  WinterXorSnow  阅读(30)  评论(0)    收藏  举报