线段树

segment tree (segtree)

目录

  • 区间查询和区间操作
  • 序列的线段树
  • 简单线段树
  • lazy 线段树

序列

  • 一些东西排成一行就成为一个序列sequence。序列里的每个东西称为一个元素,序列的元素个数称为序列的长度。一个长为 的序列 通常写作 或者
  • 序列的一段称为一个区间range。序列 的一段 通常记作 或者

区间查询和区间操作

OI 里有许多这类问题

  • 静态 RMQ
  • 单点加区间求和
  • range chmin
  • range sum range chmin
  • ……

线段树是解决这类问题的一大利器。

静态 RMQ

给你一个序列 。回答 个询问。

  • 给你 ,求 的最小值。

单点加区间求和

给你一个序列 。处理 个询问。询问有两种类型

  • add
  • sum :求

range chmin

给你一个序列 。进行 个操作。

  • :对每个 ,把 改成
    输出最终的序列

chmin 里,ch 表示 change。

range sum range chmin

给你一个序列 。处理 个询问。询问有两种类型

  • :对每个 ,把 改成
  • :求

二叉树binary tree

  • 有根树
  • 每个节点的孩子不超过两个
  • 孩子有左右之分

满二叉树full binary tree

  • 每个非叶子节点都有两个孩子。

  • 性质:有 个叶子的满二叉树一共有 个节点。

线段树

线段树是一种支持

  • 查询序列的区间信息
  • 处理对序列的区间操作

的数据结构。

序列的线段树

一个长为 的序列 的线段树:

  • 线段树的每个节点对应序列的一段;每个节点维护它所对应的那一段的某些信息。

    • 根对应整个序列;
    • 节点 的左右孩子分别同与 对应的那一段的左一半和右一半对应;不能均分时,让左一半多一项;
    • 每个叶子对应序列的一项。
  • 节点 上的信息由合并 的左右孩子上的信息得来。

线段树的结构

  • 线段树是高度平衡的满二叉树:每个节点的左右孩子的高度相差不超过

  • 除了最后一层外,每一层都是满的。

  • 一个序列的线段树的结构完全由序列长度决定。长度为 的序列的线段树

    • 个叶子, 个节点。
    • 高度是 ,有 层。

线段树的代码实现

  • 给线段树的节点编号
  • 用数组存储线段树节点的信息。

编号方案

  • level-order
  • preorder
  • inorder

level-order 编号

  • 根的编号是 ;若节点 的编号是 ,则 的左右孩子的编号分别是
  • 若一个非根节点的编号是 ,则它的父节点的编号是

线段树节点的最大编号

个叶子的线段树,采用 level-order 编号,节点的最大编号小于

  • 若最后一层的最后一个节点的编号 ,则它的父节点的编号
  • 线段树去掉最后一层就变成完美二叉树,若一个完美二叉树有 个叶子,则叶子的最大编号是
  • ,即线段树倒数第二层的节点数 ,于是线段树的叶子数大于 。矛盾!

线段树节点的最大编号

为具有 个叶子的线段树的节点的最大编号。对于任意 ,存在 满足

考虑具有 个叶子的线段树,这里 ;其节点的最大编号是 ,写成二进制是

显然,当 充分大时,

趋近于

preorder 编号

号节点不是叶子,它对应的区间是 ;令 ,那么

  • 的左孩子的编号为
  • 的右孩子的编号为

inorder 编号

  • 叶子的编号是偶数,非叶子节点的编号是奇数。
  • 个叶子的编号是
  • 号节点对应区间是 ,那么
    ,否则

在线段树上查询区间信息

  • 自顶向下,把要查询的区间拆解为线段树的节点。把这些节点上的信息合并起来,得到要查询的区间的信息。
  • 在线段树的每一层里最多访问 个点。
  • 一共访问 个点。

例一:静态 RMQ

给你一个长为 的整数序列 。依次处理 个询问:

  • :输出
限制

例子

构建线段树

  • 自底向上

查询区间最小值

  • 询问

构建线段树

const int maxn = 5e5 + 5;
int a[maxn];
int tree[4 * maxn];
// 节点x对应的区间:[l, r]
void build(int x, int l, int r) {
    if (l == r) {
        tree[x] = a[l];
        return;
    }
    int m = (l + r) / 2;
    build(x * 2, l, m);
    build(x * 2 + 1, m + 1, r);
    tree[x] = min(tree[x * 2], tree[x * 2 + 1]);
}
// build(1, 1, n)

查询区间最小值

// 节点x对应的区间:[l, r],查询区间:[ql, qr]
int query(int x, int l, int r, int ql, int qr) {
    if (r < ql || qr < l) return 1e9 + 5;
    if (ql <= l && r <= qr) return tree[x];
    int m = (l + r) / 2;
    return min(query(x * 2, l, m, ql, qr),
               query(x * 2 + 1, m + 1, r, ql, qr));
}
// query(1, 1, 10, 3, 9)

修改序列的一项

  • 修改为 :修改跟 对应的叶子,自底向上,重新计算它的每个祖先节点上的信息。
// 把 a[p] 改成 v
void set(int x, int l, int r, int p, int v) {
    if (!(l <= p && p <= r)) return;
    if (l == r) {
        tree[x] = v;
        return;
    }
    int m = (l + r) / 2;
    set(x * 2, l, m, p, v);
    set(x * 2 + 1, m + 1, r, p, v);
    tree[x] = min(tree[x * 2], tree[x * 2 + 1]);
}
// set(1, 1, 10, 2, 5)

例二:单点加区间求和

给你一个长为 的整数序列 。依次处理 个询问:

  • 0
  • 1 :输出
限制

用线段树维护区间操作

例三:区间加单点求值

给定长为 的整数序列 。处理 个询问:

  • :对于每个
  • :输出
限制

例子

区间加

const int maxn = 5e5 + 5;
long long tree[4 * maxn];
void add(int x, int l, int r, int ql, int qr, int v) {
    if (qr < l || r < ql) return;
    if (ql <= l && r <= qr) {
        tree[x] += v;
        return;
    }
    int m = (l + r) / 2;
    add(x * 2, l, m, ql, qr, v);
    add(x * 2, m + 1, r, ql, qr, v);
}

单点求值

long long get(int x, int l, int r, int p) {
    if (!(l <= p && p <= r)) return 0;
    if (l == r) return tree[x];
    int m = (l + r) / 2;
    return tree[x] + get(x * 2, l, m, p)
            + get(x * 2 + 1, m + 1, r, p);
}

例四:Range Affine Point Get

给定长为 的整数序列 。处理 个询问:

  • affine :对于每个
  • get :输出
限制

操作:可交换 vs 不可交换

  • 例一的 add 操作是可交换的而例二的 affine 操作是不可交换的。

  • 相同。

  • 不同

操作的合成

  • 然后加 相当于加
  • 然后乘 相当于乘
  • 操作 合成是这样一个操作:先进行操作 然后进行操作 ;记作
    例如,把乘 这个操作表示为 ,那么 就是

恒等操作

  • 一种操作中往往有一个不变操作,对任何一个东西施加这个操作都保持这个东西不变,称之为恒等操作
  • 例如,对于 add 操作来说,加 就是恒等操作;对于 affine 操作来说,乘 就是恒等操作。
  • 恒等操作通常记为
  • 对于任一操作 ,有

用线段树维护 range affine 操作

例子


对序列 的 range affine 操作:

affine 1 7 3 9
affine 2 8 4 7

用线段树维护 range affine 操作

例子


对序列 的 range affine 操作:

affine 1 7 3 9
affine 2 8 4 7

用线段树维护区间操作和查询区间信息

带懒标记的线段树(lazy segtree)

例五:Range Affine Range Sum

给定长为 的整数序列 。处理 个询问:

  • 0 :对于每个
  • 1 :输出
限制

例六:Point Set Range Composite

给你 个一次函数 。依次处理 个询问。

  • 0 :把 改为
  • 1 :输出
限制

例七:Range Set Range Composite

给你 个一次函数 。依次处理 个询问。

  • 0 :对每个 ,把 改为
  • 1 :输出
限制

[洛谷P3865](https://www.luogu.com.cn/problem/P3865)