题意:给定一列数,每次查询区间[s,t]中的第k大;
参考:http://www.cnblogs.com/kane0526/archive/2013/04/20/3033212.html
http://www.cnblogs.com/kuangbin/archive/2012/08/14/2638829.html
思路:快排思想+线段树=划分树,也就是树的每一层都按规则划分;
对于本题,建树前,保存原数列排序后的数列,原数列作为树的顶层;
建树时,考虑当前区间中的中间值,若大于中间值,在下一层中放右边,否则放左边,实现划分;
维护每层中1到i中小于区间中间数的个数(区间左边的数),方便查询时计算;
查询时,比较待查询区间与当前区间中左边数的个数,以便查询子区间;
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int tree[30][100010]; int as[100010]; int toleft[30][100010]; void build(int l,int r,int dep) { if(l==r)return; int mid=(l+r)/2; int same=mid-l+1;//表示等于中间值而且被分入左边的个数 for(int i=l;i<=r;i++) if(tree[dep][i]<as[mid]) same--; //目的是标记在左边的数 int lpos=l; int rpos=mid+1; for(int i=l;i<=r;i++) { if(tree[dep][i]<as[mid])//比中间的数小,分入左边 tree[dep+1][lpos++]=tree[dep][i]; else if(tree[dep][i]==as[mid]&&same>0)//相等,放左边 { tree[dep+1][lpos++]=tree[dep][i]; same--; } else //比中间值大分入右边 tree[dep+1][rpos++]=tree[dep][i]; toleft[dep][i]=toleft[dep][l-1]+lpos-l;//从1到i放左边的个数 } build(l,mid,dep+1); build(mid+1,r,dep+1); } int query(int L,int R,int l,int r,int pos,int k){ if(l==r) return tree[pos][l]; int mid=(L+R)/2; //注意 int cnt=toleft[pos][r]-toleft[pos][l-1]; //待查询区间中左边的数的个数 if(cnt>=k){ //不够,扩大区间 int newl=L+toleft[pos][l-1]-toleft[pos][L-1]; //查询区间向左移 int newr=newl+cnt-1; return query(L,mid,newl,newr,pos+1,k); } else{ //多余,缩小区间 int newr=r+toleft[pos][R]-toleft[pos][r]; //查询区间向右移 int newl=newr-(r-l-cnt); return query(mid+1,R,newl,newr,pos+1,k-cnt); } } int main(){ int n,m,i,j,k,a,b,c,t; scanf("%d",&t); while(t--){ memset(tree,0,sizeof(tree)); scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&tree[0][i]); //顶层 as[i]=tree[0][i]; } sort(as+1,as+n+1); //快排 build(1,n,0); for(i=0;i<m;i++){ scanf("%d%d%d",&a,&b,&c); printf("%d\n",query(1,n,a,b,0,c)); } } return 0; }
时间: 2024-10-05 00:22:19