可持久化线段树
用途:第\(k\)大/小问题(最经典的)
【示例】第K小问题
题目大意
给定一组序列\(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;
}
【示例】支持版本修改/查询的线段树
题目大意
要求对一个数组,能够修改其历史版本并查询
解题思路
参照之前的可持久化线段树策略,考虑对每一个版本的线段树,都新建一棵线段树(自然不是完全新建,而是跟前面一样,只关注修改的,对修改的部分加点),之后就是板子了……
示例代码
#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;
}

浙公网安备 33010602011771号