线段树是指如下图所示的数据结构:

其中,对于每个标号为n,左端点是l,右端点是r的节点有:
| 子树 | 标号 | 左端点 | 右端点 |
|---|---|---|---|
| 左子树 | 2*n | l | floor((l+r)/2) |
| 右子树 | 2*n+1 | floor((l+r)/2)+1 | r |
使用线段树,我们可以在log n时间内完成单点修改、区间修改、区间查询
下面是线段树的构建:
对于初始数组int arr[N],我们需要构建的线段树大小为4*N即可,即int tree[4*N],通过递归建立线段树:
#define lp 2*p
#define rp 2*p+1
void bulid(int p, int l, int r){ // 构建编号为p,左端点l,右端点r的线段树if(l == r){ // 叶节点,值为原始arr对应的值tree[p] = a[l];return;}int mid = (l + r)/2;build(lp, l, mid); // 构建左子树build(rp, mid+1, r); // 构建右子树tree[p] = tree[lp] + tree[rp]; // 依据左右子树更新当前节点的值
}
接下来是线段树的查找操作实现(不考虑push_back的更新):
int query(int p, int l, int r, int L, int R){ // 查询编号为p,左端点为l,右端点为r上[L,R]的和if(r < L || R < l) return 0; // 当前位置与查询范围无交集if(L <= l && r <= R) return tree[p]; // 当前位置完全包含在查询范围内// 有交集但不全在范围内:分左右子树查找int mid = (l + r)/2;return query(lp, l, mid, L, R) + query(rp, mid+1, r, L, R);
}
下面是线段树的单点修改:
修改中,我们一直递归到要修改的数据对应的叶节点,对其进行修改,然后重新递归修改其父节点的值。
void single_modify(int p, int l, int r, int Pos, int val){ // 修改pos位的值为valif(l == r){ // 到达叶节点tree[Pos] = val;return;}int mid = (l+r)/2; // 通过比较pos与mid判断修改值在左子树还是右子树if(Pos <= mid) single_modify(lp, l, mid, Pos, val); // 修改左子树else single_modify(rp, mid+1, r, Pos, val); // 修改右子树tree[p] = tree[lp] + tree[rp]; // 更新当前子树
}
下面是线段树的区间修改※:
区间修改中,我们考虑这样的做法:如果修改区间l-r,我们会标记与修改尽可能少的区间,等到查询到被影响的子区间时,再把这个标记传递下去。
如以上图为例,要让2,5上所有值都加5,那么要标记的段是:9、5、3。
这时我们需要定义一个标记数组,其大小与tree大小相同,即int tag[4*N],接下来会把每一个极大区间打上标记。
为此,我们要实现update函数和push_down函数以构建modify函数,同时对查询函数query做修改。
void update(int p, int val, int l, int r{tag[p] += val;tree[p] += (r-l+1)*val;
}
void push_up(int p){ // 向上更新tree[p] = tree[lp] + tree[rp];
}
void push_down(int p, int l, int r){ // 将节点p的懒标记向下传递if(tag[p] != 0){int mid = (l+r)/2;update(lp, tag[p], l, mid);update(rp, tag[p], mid+1, r); // 向左右子树传递}tag[p] = 0; // 清除当前标记
}
void modify(int p, int l, int r, int L, int R, int val){// p节点编号,l节点左端点,r节点右端点,给[L, R]区间上的所有元素加上valif(r < L || R < l){return;}if(L <= l && r <= R){ // 当前区间全部在更新范围内update(p, val, l, r);return;}int mid = (l+r)/2;push_down(p, l, r); // 只是有交集,懒标记必须向下传递一层modify(lp, l, mid, L, R, val);modify(rp, mid+1, r, L, R, val);push_up(p);return;
}
int query(int p, int l, int r, int L, int R){ // 查询编号为p,左端点为l,右端点为r上[L,R]的和if(r < L || R < l) return 0; // 当前位置与查询范围无交集if(L <= l && r <= R) return tree[p]; // 当前位置完全包含在查询范围内// 有交集但不全在范围内:分左右子树查找int mid = (l + r)/2;push_down(p, l, r);return query(lp, l, mid, L, R) + query(rp, mid+1, r, L, R);
}