线段树入门总结

线段树介绍:为了实现高效对一段区间进行区间修改及查询,引入一个由区间线段组成的二叉树,每个节点中保存此段区间所具体所需要的值,如这段区间里的最大值或区间和等等。复杂度非常优秀只需要以o(logn)的复杂度(树的高度)就可以实现对区间操作。

1、线段树高度是:log2(区间长度)+1,如果2^n<区间长度<2^n+1,按照n+1计算,如图

线段树区间修改分为三步:

  1. 如果这个区间被完全包括在目标区间里面,直接更新这个区间的值,例如需要给这个区间每个数加10,并记录区间和,则将区间和+=(区间右边界-左边界+1)*10,同时加上一个懒标记,同时不再往下更新。懒标记即标记自己这段区间被更新了,下一次若需要继续在这个区间往下更新或查询其子区间,需先把懒标记释放给左右儿子,把儿子所维护的区间值更新,再继续往下操作,例如刚刚在双亲结点更新了区间同时记录了懒标记,则查询其子区间时必定先将子区间先每个数也加十才是正确的记录。(懒标记是线段树的精髓也是唯一难点,可以保证所有操作复杂度都为o(logn),后续会给出详细证明)。

  2. 如果这个区间的左儿子和目标区间有交集,那么更新左儿子,如果存在懒标记,则先懒标记下推,再更新左儿子

  3. 如果这个区间的右儿子和目标区间有交集,那么更新右儿子,如果存在懒标记,则先懒标记下推,再更新右儿子

线段树区间查询分为三步:

  1. 如果这个区间被完全包括在目标区间里面,直接返回这个区间的值

  2. 如果这个区间的左儿子和目标区间有交集,那么搜索左儿子,如果存在懒标记,则先懒标记下推,再更新左儿子

  3. 如果这个区间的右儿子和目标区间有交集,那么搜索右儿子,如果存在懒标记,则先懒标记下推,再更新右儿子

单点修改和单点查询即为区间的特殊情况,所以不再此赘述。

2、最坏时间复杂度?(99%的文章不会证明)

首先懒标记的好处是不需要重复操作,多次更新,一次下推,减少了无用功。

首先,对于某一个节点,我们所查询最坏的查询情况一定是横跨其左右孩子,这样需要继续去其左右孩子中查找我们所要查找的区间,对于如下这张图而言,如果我需要查找2-14这个区间,按照上述查询规则,我需要继续往左右孩子中分别查找,而对1-8和9-16这两个孩子节点来说,如果我想时间复杂度最高,肯定也是需要继续分裂为两个子区间去查找,而关键之处在于此时我在想从1-8分裂时,其右孩子区间一定是查询规则中的第一种情况(读者可以自行思考),按照这样的逻辑继续往下查询,每一行最多只会访问四个节点,即最多为o(4log2n)。

3、线段树为什么要开四倍空间?

4、关于线段树离散化

在此给出线段树模板

// 操作是给区间每一个数加d
// 询问是求某一区间和
#include<iostream>
using namespace std;
 
typedef long long LL;
const int N = 100010;
 
int w[N];
int n, m;
 
struct Node
{
    int l, r;
    LL sum;
    LL add; // 懒标记
}tr[N * 4];;
 
void pushup(int u) // 向上传递信息
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], & right = tr[u << 1 | 1];
    if (root.add)
    {
        // 传递懒标记并且更新子树
        left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
        right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
        root.add = 0; // 删除懒标记
    }
}
void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[l], 0}; // 叶子节点
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 若不是叶子节点,向下递归
        pushup(u); // 通过子树构建父亲
    }
}
void modify(int u, int l, int r, int v)
{
    // 结点在要修改的区间中
    if (l <= tr[u].l && r >= tr[u].r)
    {
        tr[u].sum += (tr[u].r - tr[u].l + 1) * v;
        tr[u].add += v; // 加上懒标记
    }
    else
    {
        pushdown(u); // 先传递懒标记
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, v);
        if (r > mid) modify(u << 1 | 1, l, r, v);
        pushup(u); // 更新父亲
    }
}

LL query(int u, int l, int r)
{
    if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum; // 返回区间信息
    pushdown(u); // 也是先传递懒标记
    LL v = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v += query(u << 1 | 1, l, r);
    return v;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &w[i]); // 读入数组
    build(1, 1, n); // 以1为根节点,1~n区间建树
 
    char op[2];
    int l, r, t;
    
    // 读入修改和查询,q是查询,否则是修改
    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q') printf("%lld\n", query(1, l, r));
        else
        {
            scanf("%d", &t);
            modify(1, l, r, t);
        }
    }
    return 0;
}

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值