线段树 划分树 合并树 解题报告

【原】 POJ 2104 K-th Number 线段树 划分树 合并树 解题报告

作者:Allen Sun | 出处:博客园 | 阅读226次 2011/11/14 2:02:15

 

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 workinfor 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: }
上一篇:  ASPX生成HTML静态页面
下一篇:  blog开通
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值