题目:给一个长度为n的数组a1,a2...an,1<=n<=10^5,进行m次查询,1<=m<=5000;每次查询给i,j,k,表示[i,j]区间排序之后第k个数
用平方分割来做,取块大小b=sqrt(n)把整个数组分块,每个块进行排序使块内有序。排序的复杂度为O((n/b)*(n/b)\*logb)=O(nlogn)
然后求[i,j]区间第k大的数,[i,j]区间的首尾可能是部分块,是无序的,中间是有序的块。想着如何从分割后的块中高效找出第k大的数,对有序的块维护一个数对[l,r)表示第k大的数在本块中可能的位置,对无序的块直接遍历,然后...越想越觉得难以实现>,<
看了书后才发现,对原始[1,n]排序,直接在排序后的数组中进行二分搜索,对于数组中的x,高效求出[i,j]区间中<=x的数的个数c。整个排序后的数组可分为三部分c<k,c=k,c>k,而=k的最小的x即是在[i,j]区间第k大的数。是这么求第k大的数的...
对每一个x,在有序的块中用logb的时间求出<=x的个数,在无序的块中每个值进行判断,无序的值的个数为O(b),对每一个x求出c的复杂度为O((n/b)*logb+b)=O(sqrt(n)logn)。需要求c的x的个数为O(logn),算法总的复杂度看起来差不多是nlogn级别的,感觉应该能过。
但是呢,我在对不同的n取sqrt(n*logn)的b时超时了,反而直接对b取定值1000时能过...
用分桶法来做还是不够高效,桶的大小还是个问题...
然后呢,线段树就好多了,每个结点维护对应区间排序后的结果,找[i,j]区间第k大值得方法与分桶法类似,也是对排序后的数组进行二分搜索。但是求每个x对应的c在O(logn)的复杂度内即可解出。另外,STL的merge比自己写的有序数组合并的要快不少。
// bucket method
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <algorithm>
#include <functional>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
#define MAX_N 100005
const int B = 1000;
int N, M;
int A[MAX_N], SA[MAX_N];
vector<int> bucket[MAX_N / B];
int main() {
scanf("%d %d", &N, &M);
int b = 1000;
for (int i = 0; i < N; i++) {
scanf("%d", A + i);
bucket[i / b].push_back(A[i]);
SA[i] = A[i];
}
sort(SA, SA + N);
for (int i = 0; i < N / b; i++) {
sort(bucket[i].begin(), bucket[i].end());
}
for (int i = 0; i < M; i++) {
int l, r, k;
scanf("%d %d %d", &l, &r, &k);
int lo = 0, hi = N, mi;
while (lo < hi) {
mi = (lo + hi) / 2;
int x = SA[mi];
int tl = l - 1, tr = r, c = 0;
while (tl < tr && tl % b != 0) if (A[tl++] <= x) c++;
while (tl < tr && tr % b != 0) if (A[--tr] <= x) c++;
for (int j = tl / b; j < tr / b; j++) {
c += upper_bound(bucket[j].begin(), bucket[j].end(), x) -
bucket[j].begin();
}
// smallest SA[i] >= k
if (c < k) lo = mi + 1;
else hi = mi;
}
printf("%d\n", SA[lo]);
}
return 0;
}
//segment tree
#include <iostream>
#include <vector> #include <string> #include <set> #include <map> #include <stack> #include <queue> #include <algorithm> #include <functional> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> using namespace std; const int MAX_N = 100005; const int ST_SIZE = (1 << 18) - 1; int N, M; int A[MAX_N], SA[MAX_N]; vector<int> dat[ST_SIZE]; void init(int k, int l, int r) { if (r - l == 1) { dat[k].push_back(A[l]); } else { int lc = 2 * k + 1, rc = 2 * k + 2; int m = (l + r) / 2; init(lc, l, m); init(rc, m, r); dat[k].resize(r - l); merge(dat[lc].begin(), dat[lc].end(), dat[rc].begin(), dat[rc].end(), dat[k].begin()); // int i = 0, j = 0; // while (i < dat[lc].size() && j < dat[rc].size()) { // if (dat[lc][i] < dat[rc][j]) { // dat[k].push_back(dat[lc][i++]); // } else { // dat[k].push_back(dat[rc][j++]); // } // } // while (i < dat[lc].size()) dat[k].push_back(dat[lc][i++]); // while (j < dat[rc].size()) dat[k].push_back(dat[rc][j++]); } } // [a, b) [l, r) int query(int a, int b, int x, int k, int l, int r) { if (b <= l || r <= a) return 0; int res = 0; if (a <= l && r <= b) { res += upper_bound(dat[k].begin(), dat[k].end(), x) - dat[k].begin(); } else { int lc = 2 * k + 1, rc = 2 * k + 2; int m = (l + r) / 2; res += query(a, b, x, lc, l, m); res += query(a, b, x, rc, m, r); } return res; } int main() { scanf("%d %d", &N, &M); for (int i = 1; i <= N; i++) { scanf("%d", A + i); SA[i] = A[i]; } sort(SA + 1, SA + N + 1); init(0, 1, N + 1); for (int i = 0; i < M; i++) { int a, b, k; scanf("%d %d %d", &a, &b, &k); int lo = 1, hi = N + 1, md; while (lo < hi) { md = (lo + hi) / 2; int ct = query(a, b + 1, SA[md], 0, 1, N + 1); if (ct >= k) hi = md; else lo = md + 1; } printf("%d\n", SA[hi]); } return 0; }