最早见到这题是在07年JSOI冬令营上,当时知道了个大概,最近才弄明白,惭愧。。。
题意很简单,询问长度为100000的数组中给定区间上的第K大值
1. 考虑子问题,对于给定的数x,如何求出在给定区间上比x小的数有多少个(即x的排名)
2. 如果问题1得到解决,那么我们可以通过对排序过的原数组进行二分答案,直到找到一个数y,使得它在给定区间上的排名为K
本题使用线段树的思想(更严格的说,应该是归并树),树中每个节点(线段)保存该区间内原数值的有序序列。
例如原区间上的数为 2, 5, 3 那么,最终该区间上保存的数值是2,3,5. 其实并不需要存下这么多数,只需要保留一个索引(在有序数组中两端的位置)即可。这一步其实就是一个归并排序的过程。
对于给定一个数,我们查询其在给定区间上的排名。如果该区间正好覆盖了线段树的一个节点,那么直接进行二分查找即可。
如果不覆盖,只需要分别递归进入子区间,最终的排名即是两个子区间排名之和。
以上即是本题的基本算法。在我看来,这个题目的繁琐之处在于二分查找的部分。
第一个二分查找:
这是在二分答案的时候。举例: 如果给定区间上的数是2, 5, 10, 要求第三大
如果我们二分的答案是6,7,8,9,10,算出的排名都会是3. 这个时候我们要选最大的一个。
第二个二分查找:
这是在给定区间上二分查找排名的时候。其实就是查找比给定数值小的数的个数。这个时候如果遇上和给定数x相同的数,应当算作比x大来处理。
通过这个题目我知道了归并树的应用以及二分中一些细节的处理,这是以前不曾遇到的。
具体实现可参见我的代码:
- #include <iostream>
- using namespace std;
- const int N = 100010;
- struct node
- {
- int x, y;
- int dep;
- }seg[N<<3];
- int sorted[18][N];
- int n, m;
- int readData()
- {
- int ret = 0;
- char ch;
- bool dot = false,neg = false;
- while (((ch=getchar()) > '9' || ch < '0') && ch != '-') ;
- if (ch == '-') {
- neg = true;
- ch = getchar();
- }
- do {
- ret = ret*10 + (ch-'0');
- } while ((ch=getchar()) <= '9' && ch >= '0') ;
- return (neg? -ret : ret);
- }
- void build(int root, int x, int y, int depth)
- {
- seg[root] = (node){x, y, depth};
- if(x != y)
- {
- int mid = (x + y) >> 1;
- build(root * 2, x, mid, depth + 1);
- build(root * 2 + 1, mid + 1, y, depth + 1);
- int i = x, j = mid + 1, t = x;
- while(i <= mid || j <= y)
- {
- if(j > y || (i <= mid && sorted[depth+1][i] <= sorted[depth+1][j]))
- sorted[depth][t++] = sorted[depth+1][i++];
- else sorted[depth][t++] = sorted[depth+1][j++];
- }
- }
- else scanf("%d",&sorted[depth][x]);
- }
- int be, en, Kth;
- int query(int root, int x)
- {
- if(be <= seg[root].x && en >= seg[root].y)
- {
- int low = seg[root].x, up = seg[root].y;
- int d = seg[root].dep;
- if(x <= sorted[d][low]) return 0;
- else if(x > sorted[d][up]) return seg[root].y - seg[root].x + 1;
- while(low < up)
- {
- int mid = (low + up + 1) >> 1;
- if(sorted[d][mid] >= x) up = mid - 1;
- else low = mid;
- }
- return low - seg[root].x + 1;
- }
- else
- {
- int ans = 0;
- int mid = (seg[root].x + seg[root].y) >> 1;
- if(be <= mid) ans += query(root * 2, x);
- if(en > mid) ans += query(root * 2 + 1, x);
- return ans;
- }
- }
- int main()
- {
- int i, j;
- n = readData(), m = readData();
- build(1, 1, n, 0);
- for(; m; m--)
- {
- be = readData(), en = readData(), Kth = readData();
- int low = 1, up = n;
- while(up > low)
- {
- int mid = (up + low + 1) >> 1;
- if(query(1, sorted[0][mid]) >= Kth) up = mid - 1;
- else low = mid;
- }
- printf("%d/n",sorted[0][low]);
- }
- return 0;
- }