【分治】线段树 SegmentTree
算法描述
线段树是一种能够处理区间修改和区间查询的数据结构。
顾名思义,线段树就是一种存储着线段数据的树形结构。它的每个节点都表示一个线段区间,每个节点的孩子节点存储的就是该区间的左半段和右半段。每个线段区间都存储着一个值,一般是区间和,也有可能是区间最大/最小值。
算法实现
线段树使用数组实现,根节点编号为 \(1\) 表示区间 \(1\) 到 \(n\),左子节点是 \(i \times 2\),右子节点是 \(i \times 2 + 1\).
ll lc(ll p){return p << 1;}
ll rc(ll p){return p << 1 | 1;}
初始化
从根节点开始,不断的分割区间直到该区间只剩单个数,然后开始向上汇总。传入两个变量 l
和 r
, 表示当前节点表示的线段。
void pushUp(ll p){
sum[p] = sum[lc(p)] + sum[rc(p)];
}
void build(ll p, ll l, ll r){
if(l == r){
sum[p] = a[l];
return;
}
ll mid = (l + r) >> 1;
build(lc(p), l, mid);
build(rc(p), mid + 1, r);
pushUp(p);
}
区间查询
从根节点开始,如果当前的线段不被查询的区间完全包含就一直分两段尝试,直到它被完全包含了,返回该线段的值然后继续递归其他的。
ll Query(ll p, ll l, ll r, ll ql, ll qr){
if(ql <= l && qr >= r){
return sum[p];
}
pushDown(p, l, r);
ll mid = (l + r) >> 1, ans = 0;
if(ql <= mid){
ans += Query(lc(p), l, mid, ql, qr);
}
if(qr > mid){
ans += Query(rc(p), mid + 1, r, ql, qr);
}
return ans;
}
区间修改
如果每次修改都从上到下全改一遍,复杂度得 \(\mathcal{O}(n)\) ,太浪费时间了,完全体现不出线段树的优势所在。因此引入懒标记(Lazy Tag)。和查询一样,当线段被区间完全覆盖时,对应的节点自己更新,但它的孩子们不更新。这时给他安上一个懒标记,表示它的子节点需要更新的大小。当区间没有完全覆盖,需要拆分的时候,将当前变量的标记拆掉,传给子节点,这样才不会错算。
void Update(ll p, ll l, ll r, ll ql, ll qr, ll d){
if(ql <= l && qr >= r){
sum[p] += d * (r - l + 1);
tag[p] += d;
return;
}
pushDown(p, l, r);
ll mid = (l + r) >> 1;
if(ql <= mid){
Update(lc(p), l, mid, ql, qr, d);
}
if(qr > mid){
Update(rc(p), mid + 1, r, ql, qr, d);
}
pushUp(p);
}
复杂度估算
由于线段树原理是每个区间分两段,再将两端区间分别分段以此类推,又有 \(n\) 个元素,因此线段树的高度是 \(\log n\) 的。而区间修改和查询最坏都要遍历一遍每一层,因此修改和查询的复杂度都是 \(\mathcal{O}\log n\) 的,足以应付大部分题目。
总代码
题目:洛谷 P3372【模板】线段树 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll MAXN = 1e5 + 5;
ll sum[4 * MAXN], a[MAXN], tag[4 * MAXN];
ll lc(ll p){return p << 1;}
ll rc(ll p){return p << 1 | 1;}
void pushUp(ll p){
sum[p] = sum[lc(p)] + sum[rc(p)];
}
void build(ll p, ll l, ll r){
if(l == r){
sum[p] = a[l];
return;
}
ll mid = (l + r) >> 1;
build(lc(p), l, mid);
build(rc(p), mid + 1, r);
pushUp(p);
}
void moveTag(ll p, ll l, ll r, ll t){
sum[p] += t * (r - l + 1);
tag[p] += t;
}
void pushDown(ll p, ll l, ll r){
ll mid = (l + r) >> 1;
moveTag(lc(p), l, mid, tag[p]);
moveTag(rc(p), mid + 1, r, tag[p]);
tag[p] = 0;
}
void Update(ll p, ll l, ll r, ll ql, ll qr, ll d){
if(ql <= l && qr >= r){
sum[p] += d * (r - l + 1);
tag[p] += d;
return;
}
pushDown(p, l, r);
ll mid = (l + r) >> 1;
if(ql <= mid){
Update(lc(p), l, mid, ql, qr, d);
}
if(qr > mid){
Update(rc(p), mid + 1, r, ql, qr, d);
}
pushUp(p);
}
ll Query(ll p, ll l, ll r, ll ql, ll qr){
if(ql <= l && qr >= r){
return sum[p];
}
pushDown(p, l, r);
ll mid = (l + r) >> 1, ans = 0;
if(ql <= mid){
ans += Query(lc(p), l, mid, ql, qr);
}
if(qr > mid){
ans += Query(rc(p), mid + 1, r, ql, qr);
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll n, m;
cin >> n >> m;
for(ll i = 1; i <= n; i ++){
cin >> a[i];
}
build(1, 1, n);
while(m --){
ll o, x, y, k;
cin >> o;
if(o == 1){
cin >> x >> y >> k;
Update(1, 1, n, x, y, k);
}else{
cin >> x >> y;
cout << Query(1, 1, n, x, y) << "\n";
}
}
return 0;
}