线段树是一种二叉搜索树,用来区间修改维护以及查询。下面给出一道例题以便大家更好地理解线段树。
题目描述
在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:
(1)1 x y:表示修改A[x]为y;
(1)2 x y:询问x到y之间的最大值。
输入
第一行输入N(1<=N<=100000),表示序列的长度,接下来N行输入原始序列;接下来一行输入M(1<=M<=100000)表示操作的次数,接下来M行,每行为1 x y或2 x y。
输出
对于每个操作(2)输出对应的答案。
如果用暴力求解,肯定会爆,这时候我们就需要线段树了。
顾名思义,我们可以把线段树理解为一棵树。
每一个节点代表一个区间的最大值,黑色数字是每个节点所代表的区间的左右端点。红色数字是节点的编号。从图中,我们可以清晰地看出,如果一个非叶节点的编号为x,则它的左右子树的编号分别为2x和2x+1,且它的左右子树各占它的一半(有时候左右子树长度可能会相差1,不过不会有什么影响),每一个叶节点所代表的区间长度都为一。如果根节点的区间长度为n,则每一棵线段树的结点数都不超过2n-1。
我们可以用b数组来存储区间最大值。如果b[1]为根节点,则b[o]=max(b[o×2],b[o×2+1])。(想一想,为什么)
建树
我们可以递归调用bt函数,设置三个传参,分别为结点编号和结点所代表的区间的左右端点(设左端点为l,右端点为r,结点编号为o)。当l=r时,则该节点为叶节点,可以直接赋值,b[o]=a[l]。否则该节点为非叶节点,需递归调用,求出子节点的最大值后再赋值,b[o]=max(b[o×2],b[o×2+1])。建树代码:
void bt(int l,int r,int o){
if(l==r) b[o]=a[l];//赋值
else{
int mid=(l+r)/2;
bt(l,mid,o*2);
bt(mid+1,r,o*2+1);
b[o]=max(b[o*2],b[o*2+1]);
//该节点为左右子树的最大值
}
}
修改
上图,当a[3]被修改的时候,所有黄色的结点都有可能受到影响(即b[1] b[2] b[5] b[10])。这也提示我们,如果我们要把a[x]修改为y,所有包含x的结点都有可能受到影响,所以,在修改的时候,我们只需要递归调用包含y的结点进行修改。当递归到叶节点时,我们就可以直接修改。由于子节点被修改了,父节点也会受到影响,所以在修改完子节点后,我们还要重新给父节点赋值。修改代码:
void change(int l,int r,int o