线段树简单入门【更新中】

线段树简单入门

线段树

线段树的定义

线段树, 顾名思义, 就是每个节点表示一个区间.

segment1.png

线段树通常维护一些区间的值, 例如区间和.

比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和的和:

我们可以这样定义线段树的一个节点:

struct node {
    int sum; // 维护该节点表示区间的和
    int l, r; // 表示该节点表示的左右区间 (然而实现中常常不需要存储, 后面会说到)
    int lc, rc; // 表示该节点的左右孩子 (然而实现中常常不需要存储, 后面会说到)
};

实现中, 我们常常使用完全二叉树来表示线段树, (如果你写过二叉堆的话你会知道要如何表示) 而在完全二叉树中一个节点的左孩子右孩子可以很方便的求出.

(若线段树不是完全二叉树, 可以假装它是完全二叉树, 毕竟这样比较方便)

因为常常我们只需要存储一个值 sum, 于是下文只存储了一个数组 seg[] 代表所有节点的 sum 值.

线段树的基础操作

例题

Luogu 树状数组

已知一个数列,你需要进行下面两种操作:

  1. 将某一个数加上 \(x\)

  2. 求出某区间每一个数的和

\(0 \le n \le 2\times 10^5\)

更新节点

直接拿左孩子右孩子更新就可以了.

void Updata(int x) {
    seg[x] = seg[x << 1] + seg[x << 1 | 1];
}

建树

暴力建就可以了.

void Build(int x, int l, int r, int a[]) { // 给 a[] 数组建线段树
    if(l < r) {
        int mid = (l + r) >> 1;
        Build(x << 1, l, mid);
        Build(x << 1 | 1, mid + 1, r);
        Updata(x);
    } else seg[x] = a[l];
}

修改节点

递归修改, 然后更新.

// cur 表示我们目前查询到了的节点编号
// l, r 表示 cur 这个节点的编号
// 将 q 改为 k
void Modify(int cur, int l, int r, int q, int k) {
    if(ql <= l && r <= qr) { // 如果这个节点被 ql, qr 包含, 就返回这个节点的值
        seg[cur] = k;
    } else {
        int mid = (l + r) >> 1; // 左右子树的区间分别为 [l, mid], [mid + 1, r]
        if(q <= mid) Modify(cur << 1, l, mid, q, k); // 要修改的节点在左孩子
        else Modify(cur << 1 | 1, mid + 1, r, q, k); // 要修改的节点在右孩子
        Updata(cur);
    }
}

查询区间和

// cur 表示我们目前查询到了的节点编号
// l, r 表示 cur 这个节点的编号
// ql, qr 表示要查询的节点编号
int Query(int cur, int l, int r, int ql, int qr) {
    if(ql <= l && r <= qr) return seg[cur]; // 如果这个节点被 ql, qr 包含, 就返回这个节点的值
    int mid = (l + r) >> 1, ret = 0; // 左右子树的区间分别为 [l, mid], [mid + 1, r]
    if(ql <= mid) ret += Query(cur << 1, l, mid, ql, qr);
    if(qr > mid) ret += Query(cur << 1 | 1, mid + 1, r, ql, qr);
    return ret;
}

线段树的懒标记 (lazy tag)

猜你喜欢

转载自www.cnblogs.com/zhylj/p/10443242.html
今日推荐