【数据结构-树论】线段树(SMT)
引入
可以发现暴力妥妥 T 飞。
于是有新型数据结构:线段树(以下简称SMT)
定义及思想
SMT 维护区间和。
对于给定的序列 \(a\),我们可以建立一个数组 \(d\),其中 \(d_x\) 表示的是根为 \(x\) 的子树中所有数之和。
建一长为 \(2n\) 的数组,\(1\) 为根。节点 \(x\) 的左右二儿子分别为 \(2x, 2x+1\)。
实时更新。
作用
事实上,\(d\) 就是一个这样的树:
\[Pic \_1
\]
首先递归建树。
#define ls p<<1
#define rs p<<1|1
...
void build (int l, int r, int p) {
d[p].l = l, d[p].r = r;
if (l == r) d[p].v = a[l];
else {
int mid = l+r>>1;
build(l, mid, ls);
build(mid+1, r, rs);
} return ;
}
基本的 SMT 支持两个操作:区间修改 && 区间查询。
- 区间修改
若想将 \(a_l, a_{l+1}, \dots a_r\) 增加 \(k\)。
观看 \(Pic \_1\) 可以发现,想要求的结果可能在两棵子树上,所以需要挨个向下递归。
\(O(\log n)\)
void add (int l, int r, int p, int c) {
#define k d[p]
if (l<=k.l && k.r<=r) {
k.v += (k.l-k.r+1)*c;
k.b += c;
} else {
pushdown(p);
int mid = k.l+(k.r-k.l>>1);
if (l <= mid) add(l, mid, ls, c);
if (mid > r) add(mid+1, r, rs, c);
k.v = d[ls].v+d[rs].v;
} return ;
}
- 区间查询
查询必然要查最新的,而 SMT 的 feature 就是懒标记,所以在查询前需要下沉标记。
void pushdown (int p) {
#define k d[p]
if (k.b) {
int mid = k.l+(k.r-k.l>>1);
d[ls].v += k.b*(mid+1-k.l);
d[rs].v += k.b*(k.r-m);
d[ls].b += k.b;
d[rs].b += k.b;
k.b = 0;
} return ;
}
若想求 \(\Sigma^{i=l} _{r} a[i]\)。
\(O(\log n)\)
ll getsum (int l, int r, int p) {
#define k d[p]
pushdown(p);
if (l<=k.l && k.r<=r) return k.v;
ll mid = s+(t-s>>1), sum = 0;
if (l <= m) sum += getsum(l, r, s, m, p<<1);
if (r > m) sum += getsum(l, r, m+1, t, p<<1|1);
return sum;
}