【问题引入】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; } }