【数据结构-树论】线段树(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;
}
posted @ 2022-08-04 11:44  STA_Morlin  阅读(51)  评论(2编辑  收藏  举报