线段树
——解决区间类问题的利器!
线段树其实是一棵“二叉树”,其形状跟一般的二叉树没有区别。只 是在这样一棵二叉树中,每个结点记彔的是一段区间的信息。
父亲:[l,r] 左儿子:[l,(l+r)/2] 右儿子:[(l+r)/2+1,r] 那么怎么存储这样一棵线段树呢
定义如下结构体:
struct node {
int left,right; //该结点表示区间 的左右端点位置
int max,sum; //记彔这个区间的 相关信息,可以 视情况增删
};
于是,只需要开一个一维数组就可以了!tree[k]这一 点的左儿子就是tree[k*2],右儿子就是tree[k*2+1]。
Step 1:建树——初始化线段树,并将原始已知的数据写 入到线段树中。
void build(int l,int r,int k) {
tree[k].left=l;
tree[k].right=r;
if (l==r) {
tree[k].sum=a[l];
return;}
else {
int mid=(l+r)/2;
build(l,mid,k*2);
build(mid+1,r,k*2+1);
tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
}
}
其中,l,r表示当前要建的子树的根节点所表示区间 的左右端点;k表示这个节点在一维数组中的下标。主函 数中直接调用build(1,N,1)即可完成整个建树过程~
Step 2:更新——对于每一个操作1,维护线段树使得每个 结点所表示的区间信息仍然正确。
void update(int pos,int val,int k){
if (tree[k].left==tree[k].right){
tree[k].sum+=val;
return;
}
else{
int mid=(tree[k].left+tree[k].right)/2;
if (pos<=mid)
update(pos,val,k*2);
else update(pos,val,k*2+1);
tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
}
}
对于每个操作1,直接调用update(pos,val,k)就可以了, 其中pos为要更新元素在数列中的下标。
Step 3:查询——对于每一个操作2,求出所要求区间的元 素之和。
int query(int l,int r,int k){
if (tree[k].left==l && tree[k].right==r)
return tree[k].sum;
else{
int mid=(tree[k].left+tree[k].right)/2;
if (l>mid) return query(l,r,k*2+1);
else if (r<=mid) return query(l,r,k*2);
return query(l,mid,k*2)+query(mid+1,r,k*2+1);
}
}
1、线段树可以求解各种有关区间的问题,它的功能很强大。
2、空间复杂度~O(N*4),每次更新戒查询操作的复杂度都是O(logN)。
3、在更新和查询区间[l,r]的时候,为了保证复杂度是严格的O(logN)必须在达到被[l,r]覆盖的区间的结点时就立即返回。而为了保证这样做的正确性,需要在这两个过程中做一些相关的“懒”操作。有了“懒”操作就使得线段树这种结构千变万化~