P3834
【模板】可持久化线段树 2(主席树)
这题有多种解法。虽说是卡死要用可持久化线段树了,不过之后我还会再尝试一些诸如归并树,线段树套平衡树,以及离线分治。
法一:可持久化线段树
利用可持久化线段树可以非常巧妙地解决该题。
我们首先要转变观念,那就是要在值域上建立线段树,而非下标上建立线段树。当然,值域要先经过离散化。
原因就是,我们要将二分答案的过程融合进线段树的查找中,优化掉一个
然后我们建立线段树还不够,要建立一颗可持久化线段树。那么,每个历史版本到底存储了什么?修改与查询又是什么?
答案是,我们按照序列的顺序,依次向可持久化线段树中添加
而我们的查询,就是查询历史版本
还记得刚才我们建立值域线段树的目的吗?没错,是为了将二分答案的过程融合进线段树查找。现在我们就可以叙述具体的方法了。
首先,我们要了解一个原理。线段树的每个版本的根记作
接着,我们利用该原理可知,设对应节点分别为
若
那么这样,我们就能很好的二分答案得到排名为
时间复杂度
然后,如果还想支持动态修改,需要在最外层套一个树状数组,但空间复杂度较高。
代码(可持久化线段树):
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2e5;
struct sgt{
ll l,r,cnt;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define cnt(x) tree[x].cnt
}tree[N*30];
ll n,m,amt,l,r,k,tot;
ll uq[N+5],rt[N+5],a[N+5];
ll build(ll l,ll r) {
ll p=++tot;
if(l==r) return p;
ll mid=(l+r)>>1;
l(p)=build(l,mid);r(p)=build(mid+1,r);
return p;
}
ll ins(ll cur,ll l,ll r,ll loc,ll val) {
ll p=++tot;
tree[p]=tree[cur];
if(l==r) {
cnt(p)+=val;return p;
}
ll mid=(l+r)>>1;
if(loc<=mid) {
l(p)=ins(l(cur),l,mid,loc,val);
}
else {
r(p)=ins(r(cur),mid+1,r,loc,val);
}
cnt(p)=cnt(l(p))+cnt(r(p));
return p;
}
ll ask(ll p,ll q,ll l,ll r,ll k) {
if(l==r) return l;
ll mid=(l+r)>>1;
ll lcnt=cnt(l(p))-cnt(l(q));
if(k<=lcnt) return ask(l(p),l(q),l,mid,k);
else return ask(r(p),r(q),mid+1,r,k-lcnt);
}
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9') {ret=(ret<<3)+(ret<<1)+ch-'0';ch=getchar();}
return ret*f;
}
void write(ll x) {
if(x<0) {x=-x;putchar('-');}
ll y=10,len=1;
while(y<=x) {y*=10;len++;}
while(len--) {y/=10;putchar(x/y+48);x%=y;}
}
int main() {
n=read();m=read();
for(ll i=1;i<=n;i++) {
a[i]=read();uq[++amt]=a[i];
}
sort(uq+1,uq+amt+1);
tot=unique(uq+1,uq+amt+1)-uq-1;
rt[0]=build(1,amt);
for(ll i=1;i<=n;i++) {
a[i]=lower_bound(uq+1,uq+amt+1,a[i])-uq;
rt[i]=ins(rt[i-1],1,amt,a[i],1);
}
for(ll i=1;i<=m;i++) {
l=read();r=read();k=read();
write(uq[ask(rt[r],rt[l-1],1,amt,k)]);
putchar('\n');
}
return 0;
}