【原】 POJ 2104 K-th Number 线段树 划分树 合并树 解题报告
http://poj.org/problem?id=2104
方法:
1、划分树,是平衡树:数组排序nlgn,建树nlgn,m次查询mlgn,总复杂度为O(nlgn+mlgn)
划分树就是利用类似线段树的树型结构记录划分元素(最终排序)的过程。
划分树是一种树形结构的二维数组,由四个数组构成,具体见程序
建树:
从树的root(第一层)开始对原始数组进行划分,小于中位数的依原相对顺序放入左孩子节点,大于的放入右孩子节点,
并同时记录该节点(root)中截止到每个元素的分到左孩子节点的元素个数。
注意要小心处理等于中位数的元素,不能一味的将其放入左孩子或是右孩子,这样会破坏树的平衡。
再递归的对左右孩子(下一层)做同样的工作。
复杂度:T(n)=2T(n/2)+O(n)=O(n*lgn)
查询:
查询数组中某个截断a[i...j]中的第k个元素。
从root开始,计算a[i...j]中分到左孩子的元素数s。
若s<=k,则表明k-th元素在该节点的左孩子中,于是计算新的数组区间,递归的去左孩子中找新区间的第k-th元素。
若s>k,则表明k-th元素在该节点的右孩子中,于是计算新的数组区间,递归的去右孩子中找新区间的第(k-s)-th元素。
复杂度:由于每次递归都下降一层,而元素数为n的数组形成的树的高度为lgn,所以复杂度为lgn
2、归并树+2次二分+1次查询(lgn次枚举):
归并树就是利用类似线段树的树型结构记录合并排序的过程。把归并排序过程中的各区间排序后的结果,用线段树储存起来
归并树可以说是线段树+归并排序,线段树是每个区间递归下去的,而归并排序正好是拥有相似的性质,树生成即对每个
区间排好序,用一个二维数组来记录。
建树:
利用MergeSort的递归特性进行建树,从底下往上建,类似于合并的过程。复杂度nlgn
查询:
建立归并树后我们得到了序列a[1...n]的非降序排列,由于此时a[1...n]内元素对于任何区间的rank是非递减的,因此a[1...n]
中属于指定区间[b,e]内的元素的rank也是非递减的,所以我们可以用二分法枚举a[1...n]中的元素并求得它在[b,e]中的rank值,
直到该rank值和询问中的rank值相等(复杂度lgn);
那对于a[1...n]中的某个元素val,如何求得它在指定区间[s,t]中的rank?这就要利用到刚建好的归并树:我们可以利用类似线
段树的query[s,t]操作找到所有属于[s,t]的子区间(复杂度lgn),然后累加val分别在这些子区间内的rank,得到的就是val在
区间[s,t]中的rank,由于属于子区间的元素的排序结果已经记录下来,所以val在子区间内的rank可以通过二分法得到(复杂度lgn)。
上面三步经过了三次二分操作(query也是种二分),于是每次询问的复杂度是O(log n * log n * log n)
3、Partition找k-th元素。m次查询复杂度为m*n,而且为了不改变原始数组而影响下一次查询,还需要复制a[i...j]到临时数组。
复杂度太高,会导致TLE
Description
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 109 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.
1: const int N = 100001 ;
2:
3: //划分树:left,right,mid是其对应的数组的index
4: struct SegTree
5: {
6: int left ;
7: int right ;
8: int mid() { return left + ( (right-left)>>1 ) ; }
9: };
10:
11: //tree[1...N*4-1]可看做线段树,其中的下标对应关系类似于heap,1为root,2*i为左孩子,2*i+1为右孩子
12: SegTree tree[1<<19];
13: //val[0][N]...val[19][N],其中0~19对应着树的每一层,0为root层
14: int val[20][N];
15: //某节点tree[i]中的某个元素val[j][k],toleft[j][k]表示区间[ tree[i].left , val[j][k] ]中有多少个元素被分到左边
16: int toLeft[20][N];
17: //对输入数组已排序的结果,用来找中位数
18: int sortArr[N] ;
19:
20: //建立划分树,复杂度n*lgn
21: void __BuildSegTree( int l, int r, int d, int idx )
22: {
23: tree[idx].left = l ;
24: tree[idx].right = r ;
25:
26: if( l==r )
27: return ;
28:
29: int i ;
30: int lpos,rpos ;
31: int midIdx,midVal ;
32: int leftSame ; //分到左边的和midVal相同的数的个数。这样的数不能都分到左边或右边,不然会破坏树的平衡
33:
34: midIdx = tree[idx].mid() ;
35: midVal = sortArr[ midIdx ] ;
36: leftSame = midIdx - l + 1 ; //先假设分到左边的数都==midVal
37: for( i=l ; i<=r ; ++i )
38: {
39: if( val[d][i] < midVal )
40: --leftSame ;
41: }
42:
43: //由d层形成d+1层
44: lpos = l ;
45: rpos = midIdx + 1 ;
46: for( i=l ; i<=r ; ++i )
47: {
48: if( i == l )
49: toLeft[d][i] = 0 ;
50: else
51: toLeft[d][i] = toLeft[d][i-1] ;
52:
53: if( val[d][i] < midVal )
54: {
55: ++toLeft[d][i] ;
56: val[d+1][lpos++] = val[d][i] ;
57: }
58: else if( val[d][i] > midVal )
59: val[d+1][rpos++] = val[d][i] ;
60: else
61: {
62: if( leftSame >= 0 )
63: {
64: val[d+1][lpos++] = val[d][i] ;
65: ++toLeft[d][i] ;
66: --leftSame ;
67: }
68: else
69: val[d+1][rpos++] = val[d][i] ;
70: }
71: }
72:
73: //递归地对d+1层的左右子节点做构造
74: __BuildSegTree( l , midIdx , d+1 , idx*2 ) ;
75: __BuildSegTree( midIdx+1 , r , d+1 , idx*2+1 ) ;
76: }
77:
78: void BuildSegTree( int l , int r )
79: {
80: __BuildSegTree( l, r, 0, 1 ) ;
81: }
82:
83: //查询划分树中某个区间[l,r]内第k个元素
84: //复杂度lgn
85: int __Query( int l , int r , int k , int d , int idx )
86: {
87: if( l == r )
88: return val[d][l] ;
89:
90: int i ;
91: int s,ss ;
92: int b,bb ;
93: int newl,newr ; //递归的新区间
94:
95: if( l == tree[idx].left )
96: {
97: s = toLeft[d][r] ; //s为[ l , r ]中被分到左孩子的个数
98: ss = 0 ; //ss为[ tree[idx].left , l-1 ]中被分到左孩子的个数
99: }
100: else // l > tree[idx].left
101: {
102: s = toLeft[d][r] - toLeft[d][l-1] ;
103: ss = toLeft[d][l-1] ;
104: }
105:
106: if( s >= k ) // [ l , r ]中的k-th元素一定在左孩子中
107: {
108: newl = tree[idx].left + ss ;
109: newr = newl + s -1 ;
110: return __Query( newl , newr , k , d+1 , idx*2 ) ;
111: }
112: else
113: {
114: b = ( r - l +1 ) - s ; //b为[ l , r ]中被分到右孩子的个数
115: bb = ( (l-1) - tree[idx].left +1 ) - ss ; //bb为[ tree[idx].left , l-1 ]中被分到右孩子的个数
116: newl = tree[idx].mid() + 1 + bb ;
117: newr = newl + b -1 ;
118: return __Query( newl , newr , k-s , d+1 , idx*2+1 ) ;
119: }
120: }
121:
122: int Query( int l, int r, int k )
123: {
124: return __Query( l , r , k , 0 , 1 ) ;
125: }
126:
127: //由于题目中所有数字的绝对值都不相同,所以不用花费O(N)去计算分到左边的和midVal相同的个数以保持树的平衡
128: /*
129: void __BuildSegTree( int l, int r, int d, int idx )
130: {
131: tree[idx].left = l ;
132: tree[idx].right = r ;
133:
134: if( l==r )
135: return ;
136:
137: int i ;
138: int lpos,rpos ;
139: int midIdx,midVal ;
140:
141: midIdx = tree[idx].mid() ;
142: midVal = sortArr[ midIdx ] ;
143:
144: //由d层形成d+1层
145: lpos = l ;
146: rpos = midIdx + 1 ;
147: for( i=l ; i<=r ; ++i )
148: {
149: if( i == l )
150: toLeft[d][i] = 0 ;
151: else
152: toLeft[d][i] = toLeft[d][i-1] ;
153:
154: if( val[d][i] <= midVal )
155: {
156: ++toLeft[d][i] ;
157: val[d+1][lpos++] = val[d][i] ;
158: }
159: else
160: val[d+1][rpos++] = val[d][i] ;
161: }
162:
163: //递归地对d+1层的左右子节点做构造
164: __BuildSegTree( l , midIdx , d+1 , idx*2 ) ;
165: __BuildSegTree( midIdx+1 , r , d+1 , idx*2+1 ) ;
166: }
167: */
168:
169: void run2104()
170: {
171: //ifstream in("in.txt") ;
172:
173: int i,j ;
174: int n,m ;
175: int l,r,k ;
176: scanf( "%d%d" , &n,&m );
177: //in>>n>>m ;
178:
179: for( i=1 ; i<=n ; ++i )
180: {
181: scanf( "%d" , &(val[0][i]) ) ;
182: //in>>val[0][i] ;
183: sortArr[i] = val[0][i] ;
184: }
185:
186: QuickSort( sortArr+1 , n );
187: BuildSegTree( 1 , n ) ;
188:
189: /*
190: for( i=0 ; i<=4 ; ++i )
191: {
192: for( j=1 ; j<=n ; ++j )
193: cout<<val[i][j]<<" ";
194: cout<<endl ;
195: }
196: cout<<endl ;
197:
198: for( i=0 ; i<=4 ; ++i )
199: {
200: for( j=1 ; j<=n ; ++j )
201: cout<<toLeft[i][j]<<" ";
202: cout<<endl ;
203: }
204: */
205:
206:
207: while( m-- && scanf( "%d%d%d" , &l,&r,&k ) )
208: //while( m-- && in>>l>>r>>k )
209: printf( "%d\n" , Query(l,r,k) );
210: }
1: const int N = 100001 ;
2:
3: //归并树,可以看做是归并排序的产物
4: //left,right,mid是其对应的数组的index
5: struct MergeTree
6: {
7: int left ;
8: int right ;
9: int depth ;
10: int mid() { return left + ( (right-left)>>1 ) ; }
11: };
12:
13: int a[N] ;
14:
15: //tree[1...N*4-1]可看做线段树,其中的下标对应关系类似于heap,1为root,2*i为左孩子,2*i+1为右孩子
16: MergeTree tree[1<<19];
17: //val[0][N]...val[19][N],其中0~19对应着树的每一层,0为root层
18: int val[20][N];
19:
20: //由depth为d+1的两个节点Merge成depth为d的节点
21: void Merge( int d, int idx )
22: {
23: int lb,le,rb,re ;
24: int i,j ;
25: int bIdx ;
26:
27: lb = tree[idx].left ;
28: le = tree[idx].mid() ;
29: rb = le+1 ;
30: re = tree[idx].right ;
31: bIdx = lb ;
32:
33: while( lb<=le && rb<=re )
34: {
35: if( val[d+1][lb] < val[d+1][rb] )
36: val[d][bIdx++] = val[d+1][lb++] ;
37: else
38: val[d][bIdx++] = val[d+1][rb++] ;
39: }
40:
41: while( lb<=le )
42: val[d][bIdx++] = val[d+1][lb++] ;
43: while( rb<=re )
44: val[d][bIdx++] = val[d+1][rb++] ;
45: }
46:
47: void __BuildMergeTree( int d, int idx, int l, int r )
48: {
49: tree[idx].depth = d ;
50: tree[idx].left = l ;
51: tree[idx].right = r ;
52:
53: if( l == r )
54: {
55: val[d][l] = a[l] ;
56: return ;
57: }
58:
59: int midIdx = tree[idx].mid() ;
60: __BuildMergeTree( d+1 , idx*2 , l , midIdx ) ;
61: __BuildMergeTree( d+1 , idx*2+1 , midIdx+1 , r ) ;
62: Merge( d , idx );
63: }
64:
65: void BuildMergeTree( int l, int r )
66: {
67: __BuildMergeTree( 0 , 1 , l , r ) ;
68: }
69:
70: int __Query( int b, int e, int element, int idx )
71: {
72: int l,r,d ;
73: int mid ;
74: int cnt ;
75:
76: l = tree[idx].left ;
77: r = tree[idx].right ;
78: d = tree[idx].depth ;
79:
80: //找到[b...e]包含的所有子区间,停止往下递归
81: if( b<=l && e>=r )
82: {
83: //找到val[d][l...r]中<=element的最大元素的位置
84: //invairant : val[d][b]<=element<val[d][e]
85: b = l-1 ;
86: e = r+1 ;
87: while( b+1 != e )
88: {
89: mid = b + ( (e-b)>>1 ) ;
90: if( val[d][mid]<=element )
91: b = mid ;
92: else
93: e = mid ;
94: }
95: if( b == l-1 )
96: return 0 ;
97: else
98: return b-l+1 ;
99: }
100: else
101: {
102: cnt = 0 ;
103: mid = l + ( (r-l)>>1 ) ;
104: if( b<=mid )
105: cnt += __Query( b, e, element, idx*2 );
106: if( e>=mid+1 )
107: cnt += __Query( b, e, element, idx*2+1 );
108: return cnt ;
109: }
110: }
111:
112: //查找在a[b...e]中有多少个元素<=element
113: //必须是计算<=的元素数,这样才能保证所以第一个==k的元素是答案
114: int Query( int b, int e, int element )
115: {
116: return __Query( b, e, element, 1 ) ;
117: }
118:
119: void run2104_MergeTree()
120: {
121: //ifstream in("in.txt") ;
122:
123: int i,j ;
124: int n,m ;
125: int b,e,k ;
126: int mid ;
127: int cnt ;
128: int l,r ;
129: scanf( "%d%d" , &n,&m );
130: //in>>n>>m ;
131:
132: for( i=1 ; i<=n ; ++i )
133: {
134: scanf( "%d" , &a[i] ) ;
135: //in>>a[i] ;
136: //sortArr[i] =a[i] ;
137: }
138:
139: BuildMergeTree( 1 , n ) ;
140:
141: //已排好序的a[1...n]中对于某区间[b,e]可能有多个数得到的<=其本身的元素数与指定的k相同,
142: //这是由于其中有些数不在原始a[b,e]中,但是他们大于k-th number,小于k-th number的元素的<=其本身的元素数是不会等于k的
143: //例如某区间中[6,2,3],k-th number为6,小于6的5返回的<=其本身的元素数为2
144: //所以第一个==k的元素才是答案
145: //所以二分枚举时的invariant: Query(val[0][l]) < k <= Quert(val[0][r])
146: while( m-- && scanf( "%d%d%d" , &b,&e,&k ) )
147: //while( m-- && in>>b>>e>>k )
148: {
149: l = 1-1 ;
150: r = n+1 ;
151: // invariant: Query(val[0][l]) < k <= Quert(val[0][r])
152: while( l+1 != r )
153: {
154: mid = l + ( (r-l)>>1 ) ;
155: cnt = Query( b, e, val[0][mid] ) ;
156: if( cnt < k )
157: l = mid ;
158: else
159: r = mid ;
160: }
161: printf( "%d\n", val[0][r] ) ;
162: }
163: }