嗯今天来讲讲一个高端玩意,叫可持久化线段树。
新手向,有点耐心是一定可以懂的
知识储备
首先你得知道线段树是什么,不然也不需要学这个东西
线段树:(https://blog.youkuaiyun.com/floatiy/article/details/80233453)
引入
现在呢我们来思考一个问题,如果题目有需要保存线段树更改前的各个历史版本(比如给一个数列的前n项各建一棵线段树),我们应该怎么存?
每个版本存一棵树吗?
不不不太多了会爆空间的QAQ
事实上,如果要存储前我们只需要修改少量点就可以,因为每两个版本间有很多的重复部分。
圈里面是这个区间包含的数字在数列里出现的次数。
比如:现在我们有数列 2 1 3 4
黑色为只存储前1个数的线段树
现在我们要储存前两个数,完全不必完全新一棵树,可以利用原来树上和现在要建的新树一样的部分,也就是如果原来树上的某段区间在新树中仍将保持不变,那我们就把这个点(包括它的整个子树)连到新的根节点下面,从而避免了新建这些部分所用的空间和时间。
拿上面的图来说,因为数列1,2总共有两个元素而不是原来黑色树的一个,所以新建一个节点(最上面的红色那个)
然后我们发现右子树(3~4)在新树中和原来的一样,所以不做修改,直接把右子树连到新的根节点上面
再看左子树,发现发生了变化,所以创建新点,
然后以此类推,就可以得到新的线段树(红色)
p.s. 红色是前两个元素(2,1)的树
嗯大家可以自己画个图模拟一下啊
代码实现
接下来考虑如何写代码
首先我们需要建一棵各个值均为0的“空树”,这里注意一下,我们不得不使用指针,而不是像原来那样ls = pos << 1,rs = pos << 1 | 1了,因为不同的历史版本中同一个位置的点可能会有不同的编号。
Code
//by floatiy
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 200000 + 5;
int n,m;
int cnt,q;
int a[MAXN],b[MAXN],head[MAXN];
struct Tree{
int sum;
int ls,rs;
}t[MAXN << 5];
void update(int pos){
t[pos].sum = t[t[pos].ls].sum + t[t[pos].rs].sum;
return;
}
int build(int L,int R){
int pos = ++cnt;
int mid = (L + R) >> 1;
t[pos].sum = 0;
if(L < R){
t[pos].ls = build(L,mid);
t[pos].rs = build(mid + 1,R);
}
return pos;
}
int modify(int L,int R,int pre,int v){
// cout<<"DEBUG modify"<<endl;
int pos = ++cnt;
t[pos].ls = t[pre].ls;
t[pos].rs = t[pre].rs;
t[pos].sum = t[pre].sum + 1;
if(L < R){
int mid = (L + R) >> 1;
if(v <= mid) t[pos].ls = modify(L,mid,t[pre].ls,v);
else t[pos].rs = modify(mid + 1,R,t[pre].rs,v);
}
// update(pos);
return pos;
}
int query(int L,int R,int ll,int rr,int k){
if(L >= R) return L;
int mid = (L + R) >> 1;
int x = t[t[rr].ls].sum - t[t[ll].ls].sum;
if(x >= k) return query(L,mid,t[ll].ls,t[rr].ls,k);
else return query(mid + 1,R,t[ll].rs,t[rr].rs,k - x);
}
int main(){
cin>>n>>q;
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
b[i] = a[i];
}
sort(b + 1,b + 1 + n);
m = unique(b + 1,b + 1 + n) - b - 1;
head[0] = build(1,m);
for(int i = 1;i <= n;i++){
int x = lower_bound(b + 1,b + 1 + m,a[i]) - b;
head[i] = modify(1,m,head[i - 1],x);
}
// for(int i = 1;i <= cnt;i++){
// cout<<"DEBUG: "<<i<<": "<<head[i]<<" "<<t[i].ls<<" "<<t[i].rs<<" "<<t[i].sum<<endl;
// }
while(q--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
int ans = query(1,m,head[x - 1],head[y],z);
printf("%d\n",b[ans]);
}
return 0;
}