比赛描述
你为SKZ公司的数据结构部门工作,你的工作是重新写一个程序,这个程序能快速地找到一段数列中第k小的数。
就是说,给定一个整数数列a[1..n],其中每个元素都不相同,你的程序要能回答一组格式为Q (i , j , k)的查询,Q(i, j ,k)的意思是“在a[i..j]中第k小的数是多少?”
例如令 a = {1, 5, 2, 6, 3, 7, 4},查询格式为Q (2 , 5 , 3),数列段a[2..5] = {5, 2, 6, 3},第3小的数是5,所以答案是5。
输入
第一行包括一个正整数n,代表数列的总长度,还有一个数m,代表有m个查询。n、m满足:1≤n≤100 000,1≤m≤5 000。
第二行有n个数,代表数列的元素,所有数都不相同,而且不会超过109 。
接下来有m行,每行三个整数i、j、k,代表一次查询,i、j、k满足:1≤i≤j≤n, 1≤k≤j−i+1。
输出
输出每个查询的答案,用换行符隔开。
样例输入
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
样例输出
5
6
3
提示
题目来源
JSOI2010
/* 大根堆 Time Limit Exceed at Test 2 #include<iostream> #define MAX_N 100000 int a[MAX_N]; int h[MAX_N]; void maxHeapAdjust(int *a,int i,int n){ int c = (i<<1)|1; while(c<n){ if(c+1<n && a[c+1]>a[c]){ c++; } if(a[i]<a[c]){ a[i] ^= a[c]; a[c] ^= a[i]; a[i] ^= a[c]; i = c; }else{ return; } } } int main(){ int n,m,i,j,k,p; scanf("%d%d",&n,&m); for(i=0;i<n;i++){ scanf("%d",a+i); } while(m--){ scanf("%d%d%d",&i,&j,&k); i--; j--; for(p=0;p<k;p++){ h[p] = INT_MAX; } while(i<=j){ if(a[i]<h[0]){ h[0] = a[i]; maxHeapAdjust(h,0,k); } i++; } printf("%d\n",h[0]); } } */ /* 伴随数组 Time Limit Exceed at Test 2 #include<iostream> #include<algorithm> #define MAX_N 100000 struct num{ int val,index; }; bool operator<(const num n1,const num n2){ return n1.val<n2.val; } num a[MAX_N]; int main(){ int n,m,i,j,k,l; scanf("%d%d",&n,&m); for(i=0;i<n;i++){ scanf("%d",&a[i].val); a[i].index = i; } std::sort(a,a+n); while(m--){ scanf("%d%d%d",&i,&j,&k); i--; j--; for(l=0;k;){ if(a[l].index>=i && a[l].index<=j){ if(--k==0){ break; } } l++; } printf("%d\n",a[l]); } } */ /* 剪枝后的快排 Time Limit Exceed at Test 2 #include<iostream> #define MAX_N 100000 int a[MAX_N]; int b[MAX_N]; // 在c[0]至c[len-1]中,查找第k小的数 int select(int *c,int len,int k){ int i=0,j=len-1,temp=c[0]; while(i<j){ while(i<j && c[j]>=temp){ j--; } c[i] = c[j]; while(i<j && c[i]<=temp){ i++; } c[j] = c[i]; } c[i] = temp; if(i==k-1){ return c[i]; }else if(i<k-1){ return select(c+i+1,len-i-1,k-i-1); }else{ return select(c,i,k); } } int main(){ freopen("test.txt","r",stdin); int n,m,i,j,k,p,q; scanf("%d%d",&n,&m); for(i=0;i<n;i++){ scanf("%d",a+i); } while(m--){ scanf("%d%d%d",&i,&j,&k); i--; j--; for(p=0,q=i;q<=j;){ b[p++] = a[q++]; } printf("%d\n",select(b,j-i+1,k)); } } */ /* // 129MS Internet #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 100001; int sorted[MAXN]; //对原集合中元素排序后的值 int val[30][MAXN]; //val记录第k层当前位置的值 int toleft[30][MAXN]; //记录元素所在区间当前位置前的元素进入到左子树的个数 int sum[30][MAXN]; //记录比当前元素小的元素的和 int n; void build(int l, int r, int d) { if (l == r) return ; int mid = (l + r) >> 1; int same = mid - l + 1; for (int i=l; i<=r; i++) if (val[d][i] < sorted[mid]) same--; int lp = l, rp = mid+1; for (int i=l; i<=r; i++) { if (i == l) toleft[d][i] = 0; else toleft[d][i] = toleft[d][i-1]; if (val[d][i] < sorted[mid]) { toleft[d][i]++; val[d+1][lp++] = val[d][i]; } else if (val[d][i] > sorted[mid]) val[d+1][rp++] = val[d][i]; else { if (same) { same--; toleft[d][i]++; val[d+1][lp++] = val[d][i]; } else val[d+1][rp++] = val[d][i]; } } build(l, mid, d+1); build(mid+1, r, d+1); } int query(int a, int b, int k, int l, int r, int d) { if (a == b) return val[d][a]; int mid = (l + r) >> 1; int s, ss, sss; if (a == l) { s = toleft[d][b]; ss = 0; } else { s = toleft[d][b] - toleft[d][a-1]; ss = toleft[d][a-1]; } if (s >= k) { a = l + ss; b = l + ss + s - 1; return query(a, b, k, l, mid, d+1); } else { a = mid+1 + a - l - ss; b = mid+1 + b - l - toleft[d][b]; return query(a, b, k-s, mid+1, r, d+1); } } int main() { int n, m; scanf("%d %d", &n, &m); for (int i=1; i<=n; i++) { scanf("%d", &sorted[i]); val[0][i] = sorted[i]; } sort(sorted+1, sorted+1+n); build(1, n, 0); int a, b, k; while (m--) { scanf("%d%d%d", &a, &b, &k); printf("%d\n", query(a, b, k, 1, n, 0)); } return 0; } */ #include <iostream> #include <cstdio> #include <algorithm> using namespace std; #define N 100500 #define MID ((l+r)>>1) int a[N]; //原数组 int s[N]; //排序好之后的数组 int t[20][N]; //划分树第 k 层 int num[20][N]; //对于第i个数,记录在[l,i]区间内有多少数被划入左子树 int n,m; // 建树,第 c 层的 l 到 r void Build(int c,int l,int r){ int mid=(l+r)>>1; int lm=mid-l+1; //左端点到中点有多少元素,处理多个值等于s[mid] int lp=l; //左子树的第一个元素位置 int rp=mid+1; //右子树的第一个元素位置 int i; for(i=l;i<=mid;i++){ lm-=s[i]<s[mid]; //记录在[l,mid]中有多少元素等于s[mid],即左子树应该存放多少个s[mid] } for(i=l;i<=r;i++){ if( i==l ){ num[c][i]=0; }else{ num[c][i]=num[c][i-1]; // ? 对于第i个数,记录在[l,i]区间内有多少数被划入左子树 } if( t[c][i]==s[mid] ){ if( lm ){ lm--; num[c][i]++; t[c+1][lp++]=t[c][i]; }else{ t[c+1][rp++]=t[c][i]; } }else if( t[c][i]<s[mid] ){ num[c][i]++; t[c+1][lp++]=t[c][i]; } else{ t[c+1][rp++]=t[c][i]; } } if( l<r ) Build(c+1,l,mid),Build(c+1,mid+1,r); } //c:t[][]数组的层次 //l:左端点 //r:右端点 //ql:查询区间的左端点 //qr:查询区间的右端点 //k:第 k 小的数 //功能:在 t[c][] 中的[l,r]区间的子区间[ql,qr]中,查找第 k 小的数 int Query(int c,int l,int r,int ql,int qr,int k) { if( l==r ) return t[c][l]; int s,ss; if( l==ql ) s=0,ss=num[c][qr]; // s:在ql的左边有多少个元素在左子树 else s=num[c][ql-1],ss=num[c][qr]-num[c][ql-1]; //ss:[ql,qr]中有多少个在左子树 if( k<=ss ) return Query(c+1,l,MID,l+s,l+s+ss-1,k); else return Query(c+1,MID+1,r,MID+1+ql-l-s,MID+1+qr-l-s-ss,k-ss); } int main() { freopen("test.txt","r",stdin); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); s[i]=t[0][i]=a[i]; } sort(s+1,s+1+n); Build(0,1,n); while( m-- ) { int l,r,k; scanf("%d%d%d",&l,&r,&k); printf("%d\n",Query(0,1,n,l,r,k)); } return 0; }