题意
给一个数列,一些询问,问$[l,r]$中第$K$大的元素是哪一个
题解:
写法很多,主席树是最常用的一种之一
除此之外有:划分树,莫队分块,平衡树等
主席树的定义其实挺模糊,
一般认为就是可持久化线段树/函数式线段树的这种实现方式
被用来求区间第$K$大的时候,是可持久化的权值线段树
主席树的特点就是 $[l,r]$的区间信息可以通过创造的第$r$颗树的减去第$l-1$颗树得到
具体思路就是:
将数列的值进行排序,去重
对于原序列的每一个值,依次插入可持久化的权值线段树,将它所在排序位置权值+1
在线段树中,每一次插入实际上都会开一颗节点为$log(n)$新的线段树,树根保存为$rt_i$,存有$[1,i]$的区间信息
然后对于每次询问$[l,r]$,$tree_r - tree_{l-1}$的第$K$个数字即为答案
*树之间的减法是指,对于每个节点的权值进行相减
显然$tree_{i+m}-tree_i$最终得到的树与权值,就代表了$[i+1,i+m]$之间插入的元素,也就是区间$[i+1,m]$的权值树,就可以当做数列整体第$K$大解决了
#include <bits/stdc++.h> #define nd seg[now] #define ndp seg[pre] #define mid ((s+t)>>1) using namespace std; const int maxn=1e5+10; vector<int>pos; int casn,n,m,k; int num[maxn],rt[maxn],size; struct node{ int l,r,sum; }seg[maxn*20]; void maketree(int s=1,int t=n,int &now=rt[0]){ now=++size;nd={s,t,0}; if(s==t) return ; maketree(s,mid,nd.l);maketree(mid+1,t,nd.r); } void update(int &now,int pre,int k,int s=1,int t=n){ now=++size;nd=ndp,nd.sum++; if(s==t) return ; if(k<=mid)update(nd.l,ndp.l,k,s,mid); else update(nd.r,ndp.r,k,mid+1,t); } int query(int now,int pre,int k,int s=1,int t=n){ if(s==t) return s; int sum=seg[ndp.l].sum-seg[nd.l].sum; if(k<=sum) return query(nd.l,ndp.l,k,s,mid); else return query(nd.r,ndp.r,k-sum,mid+1,t); } int main(){ scanf("%d",&casn); while(casn--){ scanf("%d%d",&k,&m); pos.clear(); size=0; for(int i=1;i<=k;i++){ scanf("%d",num+i); pos.push_back(num[i]); } sort(pos.begin(),pos.end()); pos.erase(unique(pos.begin(),pos.end()),pos.end()); n=pos.size(); maketree(1,n,rt[0]); for(int i=1;i<=k;i++){ int id=lower_bound(pos.begin(),pos.end(),num[i])-pos.begin()+1; update(rt[i],rt[i-1],id); } while(m--){ int a,b,c; scanf("%d%d%d",&a,&b,&c); printf("%d ",pos[query(rt[a-1],rt[b],c)-1]); } } return 0; }