数据结构2 静态区间第K大/第K小
给定数组$A[1...N]$, 区间$[L,R]$中第$K$大/小的数的指将$A[L...R]$中的数从大到小/从小到大排序后的第$K$个.
"静态"指的是不带修改.
这个问题有多种做法:
1. 归并排序
POJ 2104, 静态区间第K小
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N(1e5+5); 5 int a[18][N]; 6 7 void merge(int id, int b, int e){ 8 int mid=(b+e)>>1; 9 for(int l=b, r=mid, i=b; i<e; i++){ 10 if(l==mid) a[id][i]=a[id+1][r++]; 11 else if(r==e) a[id][i]=a[id+1][l++]; 12 else if(a[id+1][l]<=a[id+1][r]) a[id][i]=a[id+1][l++]; 13 else a[id][i]=a[id+1][r++]; 14 } 15 } 16 17 void build(int id, int b, int e){ 18 if(b+1==e){ 19 scanf("%d", a[id]+b); 20 return; 21 } 22 int mid=(b+e)>>1; 23 build(id+1, b, mid); 24 build(id+1, mid, e); 25 merge(id, b, e); 26 } 27 //返回k在[l, r)与[L, R)交集上的Rank 28 int Rank(int id, int L, int R, int l, int r, int k){ 29 if(l<=L&&R<=r){ 30 return lower_bound(a[id]+L, a[id]+R, k)-a[id]-L; 31 } 32 int mid=(L+R)>>1; 33 if(r<=mid){ 34 return Rank(id+1, L, mid, l, r, k); 35 } 36 if(l>=mid){ 37 return Rank(id+1, mid, R, l, r, k); 38 } 39 return Rank(id+1, L, mid, l, r, k)+Rank(id+1, mid, R, l, r, k); 40 } 41 //返回(l, r]中Rank(x)>=k的最小的x 42 int BS(int b, int e, int k, int n){ 43 int l=-1e9-1, r=1e9+1, mid; 44 while(r-l>1){ 45 mid=(l+r)>>1; 46 if(Rank(0, 0, n, b, e, mid)>=k) r=mid; 47 else l=mid; 48 } 49 return r; 50 } 51 int main(){ 52 int n, m; scanf("%d%d", &n, &m); 53 build(0, 0, n); 54 for(int l, r, k; m--;){ 55 scanf("%d%d%d", &l, &r, &k), l--; 56 printf("%d\n", BS(l, r, k, n)-1); 57 } 58 }
这种做法的想法是将归并排序的过程记录下来, 这样就形成了一棵线段树, 这棵线段树的每个节点记录着它所代表的那个区间[L,R]排好序后的情形.
我们把这样的线段树称做归并排序树, 归并排序树能在$O(\log^2{N})$的复杂度内完成如下查询:
$\text{RANK}(l, r, x)$: 区间$[l,r]$内小于$x$的数的数目.
定义$\text{LEAST}(l, r, k)$为区间$[l,r]$上第$k$小的数, 则有
$\text{LEAST}(l, r, k) = \max \{x \mid \text{RANK}(l, r, x)<k\}$
因而对于询问$\text{LEAST}(l, r, x)$, 我们可以二分答案 $x$ + $\text{RANK}(l, r, x)$判断, 从而在$O(\log{M}\log^2{N})$的复杂度内完成查询, 其中$M$是元素的范围, $N$是区间总长度. 当然, 我们也可以现将数组$A[1\cdots N]$排序, 付出一个$O(N\log{N}$)的预处理复杂度, 然后便可做到单次查询$O(\log^3{N})$.
但我觉得这个复杂度还是太高了, (归并排序)这种做法应该还有优化的可能性, 有待研究.
2. 划分树