转自:
题意:给定序列,求出[s,t]区间内第k小的数字。
解法:线段树+归并排序+二分枚举
Whu回来后,开始好好看了下其余题目,感觉区间k大数应该有经典算法,问了下linle才知道poj上有原题,实在郁闷。比赛时也想到了线段树,但是没考虑到开另一个空间去保存排序后的值,所以一直没法打开思路。
(1)用线段树来表示区间,构造线段树O(NlogN),这样能在O(logN)时间内确定区间的最大值。
(2)另外保存了排序后的值后,那给定一个值,可以在区间内查找从而得出该值排序后的位置,这里可以用二分查找,复杂度又乘上了O(logN)。这儿要注意的是如果存在两个或多个相同值的时候应该输出最小的位置,自然得可以理解为并列名次。
(3)题目要求输出区间内指定名次的数值。我们从上面可以由一个值来确定名次,显然这个名次在排序后的序列中是非递减的,所以这儿又可以二分枚举,在排序后的序列中二分枚举一个数值,用(2)的方法得出名次,和指定的名次进行比较。这里的二分和(2)的二分对相同值的处理不同,这里要取相同值的最大位置,原因是当非区间内的数比区间内的数大时,才使得名次+1。
(4)最后的时间复杂度是O(MlogNlogNlogN)
听说有不需造线段树的方法,继续研究……
Sample
Num |
1 |
5 |
2 |
6 |
3 |
7 |
4 |
Sorted |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Query 1 |
2 |
3 |
5 |
6 | |||
Rank 1 |
1 |
1 |
2 |
3 |
3 |
4 |
5 |
Query 2 |
6 | ||||||
Rank 2 |
1 |
1 |
1 |
1 |
1 |
1 |
2 |
Query 3 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Rank 3 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
下载:
//POJ 2104
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
const int NMAX = 100000;
const int LOGNMAX = 17 +1;
int sortseq[LOGNMAX][NMAX];
int num[NMAX];
struct node {
int l,r,d;
node * pl,* pr;
}mem[(NMAX<<1)+100];
int mempos,n,m;
node * root;
node * make_tree(int l,int r,int d) {
node * rt = mem+(mempos ++);
rt->l = l; rt->r = r; rt->d = d;
if (l == r) {
sortseq[d][l] = num[l];
return rt;
}
int mid = (l+r) >> 1;
rt->pl = make_tree(l,mid,d+1);
rt->pr = make_tree(mid+1,r,d+1);
int i=l,j=mid+1,k=l;
while (i<=mid && j<=r) {
if (sortseq[d+1][i] < sortseq[d+1][j]) sortseq[d][k++] = sortseq[d+1][i++];
else sortseq[d][k++] = sortseq[d+1][j++];
}
while (i<=mid) sortseq[d][k++] = sortseq[d+1][i++];
while (j<=r) sortseq[d][k++] = sortseq[d+1][j++];
return rt;
}
int s,t,rank;
int query(node * rt,int val) {
int i,mid,ret;
if (s <= rt->l && rt->r <= t) {
if (val <= sortseq[rt->d][rt->l]) return 0;
else if (sortseq[rt->d][rt->r] < val) return rt->r - rt->l +1;
else if (sortseq[rt->d][rt->r] == val) return rt->r - rt->l;
int l = rt->l, r = rt->r, mid;
while (l <= r) {
mid = (l+r) >> 1;
if (val <= sortseq[rt->d][mid]) r = mid-1;
else l = mid+1;
}
return l - rt->l;
}
else {
ret = 0;
mid = (rt->l+rt->r) >> 1;
if (s <= mid) ret += query(rt->pl,val);
if (mid+1 <= t) ret += query(rt->pr,val);
return ret;
}
}
int get_val() {
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);
}int main() {
int i,j,l,r;
n = get_val(); m = get_val();
for (i=0;i<n;i++) num[i] = get_val();
mempos = 0;
root = make_tree(0,n-1,0);
while (m --) {
s = get_val()-1; t = get_val()-1; rank = get_val()-1;
l = 0, r = n-1;
while (l <= r) {
int mid = (l+r) >> 1;
int pos = query(root,sortseq[0][mid]);
if (rank < pos) r = mid-1;
else l = mid+1;
}
printf("%d\n",sortseq[0][r]);
}
}