K-th Number
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
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 first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
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
For each question output the answer to it --- the k-th number in sorted a[i...j] segment.
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
Hint
This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.
题解:
以前用主席树写过,现在学习了一下划分树。
划分树,类似线段树,主要用于求解某个区间的第k 大元素(时间复杂度log(n)),快排本也可以快速找出,但快排会改变原序列,所以每求一次都得恢复序列。
下面就以 POJ 2104 进行解说:
题目意思就是,给你n 个数的原序列,有m 次询问,每次询问给出l、r、k,求原序列l 到r 之间第k 大的数。n范围10万,m范围5千,这道题用快排也可以过,快排过的时间复杂度n*m,而划分树是m*logn(实际上应该是nlogn才对,因为建图时间是nlogn,n又比m大),分别AC后,时间相差很明显。
划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。见下图:
红点标记的就是进入左孩子的元素。
当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开val[20][N]来保存,也就是说共20层,每一层N个数。
我们还需要一个辅助数组num,num[i]表示i 前面有多少数进入左孩子(i 和i 前面可以弄成本结点内也可以是所有,两种风格不同而已,下面采取的是本结点内),和val一样,num也开成num[20][N],来表示每一层,i 和i 前面(本结点)有多少进入左孩子。
第一层:1 进入左孩子,num[1]=1,5 进入右孩子,num[2]=1,...,num[8]=4。
第二层:5 进入左孩子,num[5]=1,6 进入右孩子,num[6]=1,...,num[8]=2。
建图时就是维护每一层val[]和num[]的值就可以了。
很是清晰的,
最后那个询问其实不难,自己一开始瞎比比了一会,卡了许久时间。
这样子转换一下,多仔细考虑转换l,r那一段。
1 #include<cstring> 2 #include<cmath> 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 7 #define N 100007 8 using namespace std; 9 inline int read() 10 { 11 int x=0,f=1;char ch=getchar(); 12 while(ch<‘0‘||ch>‘9‘){if (ch==‘-‘)f=-1;ch=getchar();} 13 while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+ch-‘0‘;ch=getchar();} 14 return x*f; 15 } 16 17 int n,m; 18 int a[N],val[21][N],num[21][N]; 19 20 void build(int deep,int l,int r) 21 { 22 if (l==r) return; 23 int mid=(l+r)>>1,same=mid-l+1; 24 for (int i=l;i<=r;i++) 25 if (val[deep][i]<a[mid]) same--; 26 int lh=l,rh=mid+1; 27 for (int i=l;i<=r;i++) 28 { 29 if (i==l) num[deep][i]=0; 30 else num[deep][i]=num[deep][i-1]; 31 if (val[deep][i]<a[mid] || val[deep][i]==a[mid]&&same>0)//没有same那么所以有和a[mid]一样大的树都会进入做子树 32 { 33 val[deep+1][lh++]=val[deep][i]; 34 num[deep][i]++; 35 if (val[deep][i]==a[mid]) same--; 36 } 37 else val[deep+1][rh++]=val[deep][i]; 38 } 39 build(deep+1,l,mid),build(deep+1,mid+1,r); 40 } 41 int query(int deep,int l,int r,int x,int y,int k) 42 { 43 if (l==r) return val[deep][l]; 44 int ly,mid=(l+r)>>1; 45 if (x==l) ly=0; 46 else ly=num[deep][x-1]; 47 int sum=num[deep][y]-ly;//表示放在左边有多少。 48 if (sum>=k) return query(deep+1,l,mid,l+ly,l+num[deep][y]-1,k);//转换到这一段区间放入做子树的那一段。 49 else 50 { 51 int lr=mid+1+(x-l-ly); 52 return query(deep+1,mid+1,r,lr,lr+y-x+1-sum-1,k-sum); 53 } 54 } 55 int main() 56 { 57 while(~scanf("%d%d",&n,&m)) 58 { 59 for (int i=1;i<=n;i++) 60 a[i]=read(),val[1][i]=a[i]; 61 sort(a+1,a+n+1); 62 build(1,1,n); 63 for (int i=1;i<=m;i++) 64 { 65 int l=read(),r=read(),k=read(); 66 printf("%d\n",query(1,1,n,l,r,k)); 67 } 68 } 69 }