线段树
如图:
线段树是一种二叉搜索树,它将一个大的区间逐层划分为一个个单元区间,而最终的叶子节点代表区间中的各个点,可视为区间。划分方式是二分。为保证节点 i 的子节点是 i*2 和 i*2+1,最底层可能会浪费一些空间,因此空间为 n 的区间需要 n*4 的线段树树来储存。
综上所述,线段树可以是一棵完全二叉树,(浪费空间换来的)可以用数组来储存。
定义树的单个节点的结构体:
struct node{
int sum; // 区间和 的记录
int lazy; //区间更新的延迟标记,
int maxx; //区间的最大值
};
node tree[100010*4];
线段树的几种操作:
1.建树:
众所周知,树的建立一般采用递归的方法,线段树也不例外,所以我们会先储存叶子节点的值,而叶子节点是区间中的一个个点,它的区间和就是自身的值,因此,我们不必另外开一个数组来储存该区间,可以在递归至叶子节点时,直接读入到 sum 或 maxx 中。
一个非叶节点的 sum 是它子节点的 sum 和。
一个非叶节点的maxx是它左右子节点maxx的较大值。
上代码:
//求和建树
void set_tree(int l,int r,int poi){
//poi是节点编号,l,r是该节点代表的区间左右边界
//刚开始时 l=1,r=n,poi=1
if(l==r){ //如果左右边界相同,即为叶子节点,读入并返回
scanf("%d",&tree[poi].sum);
return;
}
int m=(l+r)/2;
set_tree(l,m,poi*2); //递归建立左子树
set_tree(m+1,r,poi*2+1); //递归建立右子树
tree[poi].sum=tree[poi*2].sum+tree[poi*2+1].sum; //初始化 sum
}
//求最大值建树
void set_tree(int l,int r,int poi){
if(l==r){
scanf("%d",&tree[poi].maxx);
return;
}
int m=(l+r)/2;
set_tree(l,m,poi*2);
set_tree(m+1,r,poi*2+1);
tree[poi].maxx=max(tree[poi*2].maxx,tree[poi*2+1].sum); //与求和建树的区别
}
2.区间求和:
这需要我们将给定区间的所有点的值相加,直接遍历肯定不行,会超时,我们看线段树,每一个叶子节点的值代表了区间的点的值,而叶子节点的值相加后成为它们父节点的 sum ,以此类推。
而我们可以利用这一特点来求给定区间的和,
挑选节点区间组成给定区间,相加区间和即可,见代码详解。
int query(int q_l,int q_r,int l,int r,int poi){
//poi为节点编号,q_l q_r是给定区间的边界,l r是节点poi代表的区间的边界
if(q_l<=l&&r<=q_r)
return tree[poi].sum;
//如果 给定区间 包含了 poi区间 ,则返回节点poi的sum值
int sum=0,m=(l+r)/2;
if(q_l<=m) //给定区间在poi左子树占有一部分
sum+=query(q_l,q_r,l,m,poi*2);//DFS,累积给定区间的和
else //给定区间在poi右子树占有一部分
sum+=query(q_l,q_r,m+1,r,poi*2+1);
return sum;
}
3.区间求最大值:
同样的,寻找节点区间来表示给定区间,再用所找到的节点区间的maxx值求出给定区间的最大值。
见代码:
int query(int q_l,int q_r,int l,int r,int poi){
if(q_l<=l&&r<=q_r)
return tree[poi].maxx;
int ans_max=0,m=(l+r)/2;
if(q_l<=m)
ans_max=max(ans_max,query(q_l,q_r,l,m,poi*2));
if(m<q_r)
ans_max=max(ans_max,query(q_l,q_r,m+1,r,poi*2+1));
return ans_max;
//解析同区间求和
}
4.单点更新(只考虑对求和的影响):
将 给定点的值 增加 给定的另一个值,如将点x的值增加y。
从根节点开始采用二分的方法找到该点在树中对应的叶子节点,更新该叶子节点的sum值后,递归回去,更新受影响的节点的sum值。
见代码:
void poi_add(int x,int y,int l,int r,int poi){
//poi是当前节点编号,l r是poi区间边界,x是要求更新的点,y是要求增加的值。
if(l==r){