(零基础者出门左拐)
最近学了主席树,打了几道模板题。感觉还行
主席树,在我看来就是线段树的可持化 (一开始以为主席树只是可持久化权值线段树)。在题目中需要建多颗线段树或权值线段树且,相邻的线段树差别不大(一般就一个点不一样)时就可以用主席树。运用可持久化的思想,我们并不需要重新构建一颗线段树,因为只需要改一个点,所以线段树只需要新多出\(logn\)个节点,其他的节点继承前面的线段树就行了(所以一般都要开始建一颗空树)。这样一来,我们建树的时间复杂度和、这一堆线段树的空间复杂度变成了\(nlogn\),真是佩服人类的智慧。
嗯,模板题,求区间第k大,我们找到对应的两颗线段树\(root[r]\)和\(root[l-1]\)然后在这两颗线段数上同时二分就行了。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=2e5+100; int num,n,m,a[N],b[N],tot,root[N],w[N*20],ch[N*20][2]; void build(int l,int r,int &now){ now=++num; if(l==r)return; int mid=(l+r)>>1; build(l,mid,ch[now][0]); build(mid+1,r,ch[now][1]); } void ins(int l,int r,int x,int pre,int &now){ now=++num; w[now]=w[pre]+1; if(l==r)return; ch[now][0]=ch[pre][0]; ch[now][1]=ch[pre][1]; int mid=(l+r)>>1; if(x>mid)ins(mid+1,r,x,ch[pre][1],ch[now][1]); else ins(l,mid,x,ch[pre][0],ch[now][0]); } int check(int l,int r,int k,int pre,int now){ if(l==r)return l; int tmp=w[ch[now][0]]-w[ch[pre][0]]; int mid=(l+r)>>1; if(tmp>=k)return check(l,mid,k,ch[pre][0],ch[now][0]); else return check(mid+1,r,k-tmp,ch[pre][1],ch[now][1]); } int read(){ int sum=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();} return sum*f; } int main(){ n=read();m=read(); for(int i=1;i<=n;i++)a[i]=read(),b[++tot]=a[i]; sort(b+1,b+1+tot); tot=unique(b+1,b+1+tot)-b-1; for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+1+tot,a[i])-b; build(1,tot,root[0]); for(int i=1;i<=n;i++)ins(1,tot,a[i],root[i-1],root[i]); while(m--){ int l=read(),r=read(),k=read(); printf("%d\n",b[check(1,tot,k,root[l-1],root[r])]); } return 0; }
非常短