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

【原】 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开通
内容概要:本文详细介绍了一种基于Simulink的表贴式永磁同步电机(SPMSM)有限控制集模型预测电流控制(FCS-MPCC)仿真系统。通过构建PMSM数学模型、坐标变换、MPC控制器、SVPWM调制等模块,实现了对电机定子电流的高精度跟踪控制,具备快速动态响应和低稳态误差的特点。文中提供了完整的仿真建模步骤、关键参数设置、核心MATLAB函数代码及仿真结果分析,涵盖转速、电流、转矩和三相电流波形,验证了MPC控制策略在动态性能、稳态精度和抗负载扰动方面的优越性,并提出了参数自整定、加权代价函数、模型预测转矩控制和弱磁扩速等优化方向。; 适合人群:自动化、电气工程及其相关专业本科生、研究生,以及从事电机控制算法研究与仿真的工程技术人员;具备一定的电机原理、自动控制理论和Simulink仿真基础者更佳; 使用场景及目标:①用于永磁同步电机模型预测控制的教学演示、课程设计或毕业设计项目;②作为电机先进控制算法(如MPC、MPTC)的仿真验证平台;③支撑科研中对控制性能优化(如动态响应、抗干扰能力)的研究需求; 阅读建议:建议读者结合Simulink环境动手搭建模型,深入理解各模块间的信号流向与控制逻辑,重点掌握预测模型构建、代价函数设计与开关状态选择机制,并可通过修改电机参数或控制策略进行拓展实验,以增强实践与创新能力。
根据原作 https://pan.quark.cn/s/23d6270309e5 的源码改编 湖北省黄石市2021年中考数学试卷所包含的知识点广泛涉及了中学数学的基础领域,涵盖了实数、科学记数法、分式方程、几何体的三视图、立体几何、概率统计以及代数方程等多个方面。 接下来将对每道试题所关联的知识点进行深入剖析:1. 实数与倒数的定义:该题目旨在检验学生对倒数概念的掌握程度,即一个数a的倒数表达为1/a,因此-7的倒数可表示为-1/7。 2. 科学记数法的运用:科学记数法是一种表示极大或极小数字的方法,其形式为a×10^n,其中1≤|a|<10,n为整数。 此题要求学生运用科学记数法表示一个天文单位的距离,将1.4960亿千米转换为1.4960×10^8千米。 3. 分式方程的求解方法:考察学生解决包含分母的方程的能力,题目要求找出满足方程3/(2x-1)=1的x值,需通过消除分母的方式转化为整式方程进行解答。 4. 三视图的辨认:该题目测试学生对于几何体三视图(主视图、左视图、俯视图)的认识,需要识别出具有两个相同视图而另一个不同的几何体。 5. 立体几何与表面积的计算:题目要求学生计算由直角三角形旋转形成的圆锥的表面积,要求学生对圆锥的底面积和侧面积公式有所了解并加以运用。 6. 统计学的基础概念:题目涉及众数、平均数、极差和中位数的定义,要求学生根据提供的数据信息选择恰当的统计量。 7. 方程的整数解求解:考察学生在实际问题中进行数学建模的能力,通过建立方程来计算在特定条件下帐篷的搭建方案数量。 8. 三角学的实际应用:题目通过在直角三角形中运用三角函数来求解特定线段的长度。 利用正弦定理求解AD的长度是解答该问题的关键。 9. 几何变换的应用:题目要求学生运用三角板的旋转来求解特定点的...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值