【问题引入】N个数字组成的序列,M个询问,每次求区间[L,R]中的第K大的数(HDU2665)

线段树?只能维护最值。

求一段序列的第K大,可用权值线段树实现。

利用前缀和思想,建立N棵线段树,但这样显然会MLE。

考虑到每两棵相邻的线段树之间只做了单点+1的修改,只有少量的点维护的信息发生了更改,所以没有更改的节点可以直接指过去。

具体见代码。(注意:节点数要开到N的20倍)

#include <bits/stdc++.h>
using namespace std;
const int N=100005,M=2000005;
int root[N]={0},L[M]={0},R[M]={0},s[M]={0},m,x,y,k,a[N],n,cnt;
//root[i]保存第i棵权值线段树的根节点序号,L[i],r[i]分别是每个节点的左右儿子序号,s[i]为权值线段树维护的信息 
void Update(int &A,int &B,int l,int r,int k){
    //用以节点A所根的子树做模板,构造以节点B为根的子树
    B=++cnt;//创立新节点 
    s[B]=s[A]+1;//权值线段树累和 
    if (l==r) return;
    int mid=(l+r)/2;
    //对比两棵相邻的线段树,信息有改变的指向新节点,不变的指向同一节点 
    if (k<=mid) Update(L[A],L[B],l,mid,k);
    else Update(R[A],R[B],mid+1,r,k);
    if (!R[B]) R[B]=R[A];
    if (!L[B]) L[B]=L[A];
}
int Find(int A,int B,int l,int r){
    if (l==r) return l;//找到第k大 
    int mid=(l+r)/2;
    if (s[R[B]]-s[R[A]]>=k)//前缀和思想 
    return Find(R[A],R[B],mid+1,r);//权值[mid+1,r]在[x,y]中出现次数>=k
    else{
        k-=(s[R[B]]-s[R[A]]);
        return Find(L[A],L[B],l,mid);
    }
}
int main(){
    cin>>n>>m;
    for (int i=1; i<=n; i++) cin>>a[i];
    for (int i=1; i<=n; i++)Update(root[i-1],root[i],1,100000,a[i]);
    while (m--){
        scanf("%d%d%d",&x,&y,&k);
        cout<<Find(root[x-1],root[y],1,100000)<<endl;
    }
}