K-th Number(POJ 2104)
- 原题如下:
K-th Number
Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 68433 Accepted: 24193 Case Time Limit: 2000MS Description
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.Input
The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).Output
For each question output the answer to it --- the k-th number in sorted a[i...j] segment.Sample Input
7 3 1 5 2 6 3 7 4 2 5 3 4 4 1 1 7 3
Sample Output
5 6 3
Hint
This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed. - 题解1:
- 如果x是第k个数,那么一定有①在区间中不超过x的数不少于k ②在区间中小于x的数有不到k个,因此,只要快速求出区间里不超过x的数的个数,就可以通过对x进行二分搜索来求出第k个数是多少。如果不进行预处理,就只能遍历一遍所有元素,如果每个区间有序,可以用二分高效地求不超过x的数的个数,但每个查询做一次排序无法降低复杂度。综上,考虑平方分割或线段树求解。
平方分割:把数列每b个一组分到各个桶里,每个桶内保存排好序的数列。这样的话,如果要求在某个区间内不超过x的数的个数就可以①对于完全包含在区间内的桶用二分搜索计算②对于所在的桶不完全包含在区间内的元素,逐个检查,如果把b设为√n,复杂度就为O((n/b)logb+b)=O(√nlogn)。其中对于每个元素的处理只需要O(1)的时间,而对于每个桶的处理则需要O(logb),为了让程序更加高效,应该把桶的数量设置成比桶内元素个数略少一些。如果把b设为√(nlogn),复杂度就变为O((n/b)logb+b)=O(√(nlogn)),接下来只需要对x进行二分搜索就行了,二分搜索需要执行O(logn)次,因此,如果b=√(nlogn),包括预处理在内的整个算法的复杂度就是O(nlogn+m(√n)log1.5n)。
- 如果x是第k个数,那么一定有①在区间中不超过x的数不少于k ②在区间中小于x的数有不到k个,因此,只要快速求出区间里不超过x的数的个数,就可以通过对x进行二分搜索来求出第k个数是多少。如果不进行预处理,就只能遍历一遍所有元素,如果每个区间有序,可以用二分高效地求不超过x的数的个数,但每个查询做一次排序无法降低复杂度。综上,考虑平方分割或线段树求解。
- 代码1:
1 #include <cstdio> 2 #include <cctype> 3 #include <cmath> 4 #include <algorithm> 5 #include <cstring> 6 #define number s-'0' 7 using namespace std; 8 9 const int MAX_N=100005; 10 int N,M; 11 int A[MAX_N], nums[MAX_N], bucket[MAX_N]; 12 13 void read(int &x){ 14 char s; 15 x=0; 16 bool flag=0; 17 while(!isdigit(s=getchar())) 18 (s=='-')&&(flag=true); 19 for(x=number;isdigit(s=getchar());x=x*10+number); 20 (flag)&&(x=-x); 21 } 22 23 void write(int x) 24 { 25 if(x<0) 26 { 27 putchar('-'); 28 x=-x; 29 } 30 if(x>9) 31 write(x/10); 32 putchar(x%10+'0'); 33 } 34 35 int main() 36 { 37 read(N);read(M); 38 for (int i=0; i<N; i++) read(A[i]); 39 int B=sqrt(N*log(N+0.0)+0.0); 40 if (!B) B++; 41 memcpy(nums, A, sizeof(A)); 42 memcpy(bucket, A, sizeof(A)); 43 sort(nums, nums+N); 44 for (int i=0; i+B<=N; i+=B) sort(bucket+i, bucket+i+B); 45 int l, r, k; 46 for (int i=0; i<M; i++) 47 { 48 read(l);read(r);read(k); 49 l--; 50 int s=(l/B+1)*B, e=r/B*B; 51 int lb=-1, ub=N-1, md, x; 52 while (ub-lb>1) 53 { 54 md=(lb+ub)/2; 55 x=nums[md]; 56 int c=0; 57 for (int i=l; i<s && i<r; i++) if (A[i]<=x) c++; 58 for (int i=e; i<r && i>l; i++) if (A[i]<=x) c++; 59 for (int i=s; i+B<=e; i+=B) 60 { 61 c+=upper_bound(bucket+i, bucket+i+B, x)-bucket-i; 62 } 63 if (c>=k) ub=md; 64 else lb=md; 65 } 66 write(nums[ub]);putchar('\n'); 67 } 68 }
- 题解2:线段树:把数列用线段树维护起来,线段树的每个节点保存对应区间排好序后的结果。这里和之前接触到的线段树节点保存数值不同,这里每个节点保存了一个数列。建立线段树的过程和归并排序的类似,而每个节点的数列就是其两个儿子节点的数列合并后的结果。建树的复杂度是O(nlogn)。这棵线段树正是归并排序的完整再现,所以这样的线段树也叫归并树。要计算在某个区间中不超过x的数的个数,只需要递归地进行如下操作:①如果所给区间和当前节点的区间完全没有交集,那么返回0 ②如果所给区间完全包含了当前节点对应的区间那么使用二分搜索对该节点上保存的数列进行查找 ③否对对两个儿子递归地进行计算之后求和即可。由于对于同一深度的节点最多只访问常数个,因此可以在O(log2n)时间里求出不超过x的数的个数。所以整个算法的复杂度是O(nlogn+mlog3n)。
- 代码2:
1 #include <cstdio> 2 #include <cctype> 3 #include <cmath> 4 #include <vector> 5 #include <algorithm> 6 #define number s-'0' 7 8 using namespace std; 9 10 const int ST_SIZE=(1<<18)-1; 11 const int MAX_N=100000; 12 const int MAX_M=5000; 13 int N,M; 14 int A[MAX_N]; 15 int I[MAX_M], J[MAX_M], K[MAX_M]; 16 int nums[MAX_N]; 17 vector<int> dat[ST_SIZE]; 18 19 void read(int &x){ 20 char s; 21 x=0; 22 bool flag=0; 23 while(!isdigit(s=getchar())) 24 (s=='-')&&(flag=true); 25 for(x=number;isdigit(s=getchar());x=x*10+number); 26 (flag)&&(x=-x); 27 } 28 29 void write(int x) 30 { 31 if(x<0) 32 { 33 putchar('-'); 34 x=-x; 35 } 36 if(x>9) 37 write(x/10); 38 putchar(x%10+'0'); 39 } 40 41 void solve(); 42 void init(int k, int l, int r); 43 int query(int i, int j, int x, int k, int l, int r); 44 45 int main() 46 { 47 read(N);read(M); 48 for (int i=0; i<N; i++) read(A[i]); 49 for (int i=0; i<M; i++) {read(I[i]);read(J[i]);read(K[i]);} 50 solve(); 51 } 52 53 void solve() 54 { 55 for (int i=0; i<N; i++) nums[i]=A[i]; 56 sort(nums, nums+N); 57 init(0, 0, N); 58 for (int i=0; i<M; i++) 59 { 60 int l=I[i]-1, r=J[i], k=K[i]; 61 int lb=-1, ub=N-1; 62 while (ub-lb>1) 63 { 64 int md=(ub+lb)/2; 65 int c=query(l, r, nums[md], 0, 0, N); 66 if (c>=k) ub=md; 67 else lb=md; 68 } 69 write(nums[ub]);putchar('\n'); 70 } 71 } 72 73 void init(int k, int l, int r) 74 { 75 if (r-l==1) dat[k].push_back(A[l]); 76 else 77 { 78 int lch=k*2+1, rch=k*2+2; 79 init(lch, l, (l+r)/2); 80 init(rch, (l+r)/2, r); 81 dat[k].resize(r-l); 82 merge(dat[lch].begin(), dat[lch].end(), dat[rch].begin(), dat[rch].end(), dat[k].begin()); 83 } 84 } 85 86 int query(int i, int j, int x, int k, int l, int r) 87 { 88 if (j<=l || r<=i) return 0; 89 else if (i<=l && r<=j) return upper_bound(dat[k].begin(), dat[k].end(), x)-dat[k].begin(); 90 else 91 { 92 int lc=query(i, j, x, k*2+1, l, (l+r)/2); 93 int rc=query(i, j, x, k*2+2, (l+r)/2, r); 94 return lc+rc; 95 } 96 }