线段树一般用来做单点修改
和区间查询
操作
树状数组是多叉树,线段树是一个二叉树
struct Node{
int l,r;//l,r属于1~n ,是原数组的下标(从1开始)
int sum;
} tr[N * 4];
int w[N];
单点修改 O(logn)
本质是一个递归和回溯的过程
区间查询 O(logn)
与前缀和对比
单点修改 | 区间查询 | |
---|---|---|
前缀和 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
线段树 | O ( l o g n ) O(logn) O(logn) | O ( l o g n ) O(logn) O(logn) |
主要方法
pushup | 用子节点信息更新当前节点信息 |
---|---|
build | 在一段区间上初始化线段树 |
modify | 修改 |
query | 查询 |
pushup
u << 1 = 2 * u
u << 1 | 1 = 2 * u + 1
void pushup(int u) {//其实就是回溯的过程,每个结点的sum值等于左右儿子的sum值之和
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
build
void build(int u, int l, int r) {
if(l == r) tr[u] = {l, r, w[r]}; // 叶子节点直接赋值
else {
tr[u] = {l,r};//下面的pushup操作只能更新sum值,所以还需要将这一个结点的l和r赋值
int mid = l + r >> 1;
build(u << 1, l, mid); // 更新左儿子
build(u << 1 | 1, mid + 1, r); // 更新右儿子
pushup(u); // 把信息往上传递更新父节点
}
}
modify
void modify(int u, int x, int v) {
if(tr[u].l == tr[u].r) tr[u].sum += v; // 叶子节点直接加上这个v
else {
int mid = tr[u].l + tr[u].r >> 1;
//注意:递归时x,v都不变
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);//往上回溯,更新所有经过节点的sum值
}
}
query
int query(int u, int l,int r) {
//如果序列完全包含这个子区间 则直接返回
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
int mid = tr[u].l + tr[u].r >> 1;
int sum = 0;
//注意query递归时的参数l,r都不发生变化 ,变化的参数只是左右儿子的坐标u
if(l <= mid) sum = query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}