Description
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 second line contains n different integer numbers not exceeding 10 9 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
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
题意:给定一个正整数序列 a,每次给出一个查询 (i, j, k),输出子序列 a[i...j]按升序排列后的第 k个数
方法一:分桶法。即用平方分割,将序列每b个放在一个“桶”里,每个桶里元素都按照升序排列。如果x是第k个数,那么区间[i, j]内不超过x的元素一定不少于k个,因此如果可以快速求出不超过x的元素有多少个的话就可以对x进行二分搜索。对于区间[i, j],里面包含完整的一个桶的子区间因为是升序排列,可以二分搜索不超过x的元素个数。而对于两端不完整的子区间,直接遍历即可。时间复杂度为O((n/b)*logb+b),那么b取多少合适呢?如果按照传统的平方分割法取b=sqrt(n),那么结果会超时。。。最好的取法是b=sqrt(nlogn),也就是b取1000左右合适。
方法二:线段树。每个节点维护对应区间升序排列的数列,初始化过程其实就是归并排序!查询时二分搜索即可。
两种方法时间分别为11s和6s,不知道大牛们1s不到是怎么优化的, 慢慢学习吧。
分桶法实现:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <limits>
#include <new>
#include <utility>
#include <iterator>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;
const int B = 1000; //桶的大小
const int maxn = 100010;
int n, m;
int A[maxn];
int nums[maxn]; //对A排序后的结果
vector<int> bucket[maxn/B]; //每个桶排序后的结果
void solve()
{
for (int i = 0; i < n; ++i)
{
bucket[i/B].push_back(A[i]);
nums[i] = A[i];
}
sort(nums, nums+n);
//最后一个组没有排序,但是没有影响
for (int i = 0; i < n/B; ++i)
sort(bucket[i].begin(), bucket[i].end());
while (m--)
{
//求[l, r)中的第k个数
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
l--;
int lb = -1, ub = n - 1;
while (ub - lb > 1)
{
int md = (lb + ub) >> 1;
int x = nums[md];
int tl = l, tr = r, c = 0;
//区间两端多出的部分直接遍历即可
while (tl < tr && tl % B)
if (A[tl++] <= x)
c++;
while (tl < tr && tr % B)
if (A[--tr] <= x)
c++;
//每一个桶内都二分搜索
while (tl < tr)
{
int b = tl / B;
c += upper_bound(bucket[b].begin(), bucket[b].end(), x) - bucket[b].begin();
tl += B;
}
if (c >= k)
ub = md;
else
lb = md;
}
printf("%d\n", nums[ub]);
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i)
scanf("%d", &A[i]);
solve();
return 0;
}
线段树实现:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <limits>
#include <new>
#include <utility>
#include <iterator>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;
const int st_size = (1 << 18) - 1;
const int maxn = 100010;
int n, m;
int A[maxn];
int nums[maxn]; //对A排序后的结果
vector<int> dat[st_size];
//构建线段树
//k是节点的编号,和区间[l, r)对应
void init(int k, int l, int r)
{
if (r - l == 1)
dat[k].push_back(A[l]);
else
{
int lch = k * 2 + 1, rch = k * 2 + 2;
init(lch, l, (l+r)>>1);
init(rch, (l+r)>>1, r);
dat[k].resize(r-l);
//利用STL的merge函数把两个儿子的数列合并
merge(dat[lch].begin(), dat[lch].end(), dat[rch].begin(), dat[rch].end(), dat[k].begin());
}
}
//计算[i, j)中不超过x的数的个数
//k是节点的编号,和区间[l, r)对应
int query(int i, int j, int x, int k, int l, int r)
{
//完全不相交
if (j <= l || i >= r)
return 0;
//完全包含
if (i <= l && j >= r)
return upper_bound(dat[k].begin(), dat[k].end(), x) - dat[k].begin();
//对儿子递归计算
int lc = query(i, j, x, k*2+1, l, (l+r)>>1);
int rc = query(i, j, x, k*2+2, (l+r)>>1, r);
return lc + rc;
}
void solve()
{
for (int i = 0; i < n; ++i)
nums[i] = A[i];
sort(nums, nums+n);
init(0, 0, n);
while (m--)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
l--;
int lb = -1, ub = n - 1;
while (ub - lb > 1)
{
int md = (ub + lb) >> 1;
int c = query(l, r, nums[md], 0, 0, n);
if (c >= k)
ub = md;
else
lb = md;
}
printf("%d\n", nums[ub]);
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; ++i)
scanf("%d", &A[i]);
solve();
return 0;
}