处理RMQ问题的一个实用的数据结构是线段树,它是一种在每个节点上维护区间信息的二叉搜索树,并且由于一般不删除结点,平衡性较好,因而能实现高效查找、修改的目的。
线段树的根节点维护整个区间的信息,每个结点的左右儿子分别维护父节点左半部分与右半部分的区间信息。
下面以单点增加值与区间查询最大值为例说明如何利用线段树完成RMQ操作:
为了方便起见,修改与查询均传入每个结点对应的区间。
1.建立:为保险起见,将线段树大小设为四倍的区间大小。
int seg_tree[M*4],num_nodes;
//...
//计算有效结点个数
for(int i=0; (1<<i)<n; i++);
num_nodes=1<<(i+1)-1;
int t=(1<<n)-1;
2.查询:从根节点开始:
- 若结点对应的区间与待查区间完全无交集,返回-INF;
- 若结点对应的区间完全包含于待查区间,返回该节点的值;
- 其他情况,递归地查找左右子树,并返回其中的较小者。
int query(int st,int end,int k=0,int l=0,int r=t) {
if(l>end||r<st) return -INF;
if(l>=st&&r<=end) return seg_tree[k];
int L=query(st,end,k*2+1,l,(l+r)/2);
int R=query(st,end,k*2+2,1+(l+r)/2,r);
return max(L,R);
}
复杂度 O ( l o g n ) O(logn) O(logn)
3.更新:按照查询思路更新
void update(int p,int val,int k=0,int l=0,int r=t) {
if(p>r||p<l) return;
if(p>=l&&p<=r) {
seg_tree[k]+=val;
return;
}
update(p,val,k*2+1,l,(l+r)/2);
update(p,val,k*2+2,1+(l+r)/2);
seg_tree[k]=max(seg_tree[k*2+1],seg_tree[k*2+2]);
}
复杂度 O ( l o g n ) O(logn) O(logn)