zoukankan      html  css  js  c++  java
  • 线段树板子(懒惰标记)

    线段树

    • 概念

      • 线段树就是在二叉树的基础上,每个节点存储一个区间中所有数的和。
      • 如果一个节点对应的区间是 ([l, r]),那么把 ([l, r]) 分成(left [l,left lfloor frac{l+r}{2} ight floor ight ])(左儿子)和 (left [ left lfloor frac{l+r}{2} ight floor +1,r ight ])(右儿子). 根节点表示的区间是[1, n],这样区间 [1, n] 就被划分为一个树形结构.
      • 需要注意的还有线段树数组的大小:
        • 线段树的深度是 (left lceil logn ight ceil).
        • 线段树是一棵完全二叉树,总节点个数为 (2 ^ {left lceil logn ight ceil + 1} - 1 < 4n).
          因此我们至少要把线段树的节点数开成 (4n).
    • 建树

      • 根据二叉树的性质,rt的左儿子是rt×2,右儿子是rt×2+1,用位运算可以优化时间效率
      • code
        void build(int rt, int l, int r) {
            if (l == r) { t[rt] = a[l]; return; }//叶子节点,递归边界
            int mid = (l + r) >> 1;
            build(rt << 1, l, mid);//递归建立左子树
            build(rt << 1 | 1, mid + 1, r);//递归建立右子树
            t[rt] = t[rt<<1] + t[rt<<1|1];
        }
        
    • 单点修改

      • 修改线段树上的一个节点最多只会影响该节点到根的路径上的这些节点,也就是最多只需要修改(left lfloor logn ight floor)个节点。

      • code

        void change(int rt, int l, int r) {
            if (l == r) {r[rt] += y; return; }//叶子节点,递归边界
            int mid = (l + r) >> 1;
            if (x <= mid) change(rt << 1, l, mid);
            //x <= mid 说明要修改的数字在左子树
            else change(rt << 1 | 1, mid + 1, r);
            //x <= mid 说明要修改的数字在右子树
            t[rt] = t[rt>>1] + t[rt>>1|1];
        }
        
    • 区间查询

      • code
        int sum(int rt, int l, int r) {
            if (x <= l && r <= y) return t[rt];
            //区间[x, y]完全覆盖线段树[l, r]
            int mid = (l + r) >> 1, ans = 0;
            if (x <= mid) ans += sum(rt << 1, l, mid);
            //区间[x, y]与左儿子[l, mid]有交
            if (y > mid) ans += sum(rt << 1 | 1, mid + 1, r);
            //区间[x, y]与右儿子[mid + 1, r]有交
            return ans;
        }
        
    • 懒惰标记

      • 思考:如果对一个区间的每一个数进行修改,复杂度是(O(nlogn)),有没有更优的解法?

      • 修改和查询一样,可以将其拆成若干个极大的区间,分别修改这些区间,那就要用到今天的重点——懒惰标记

      • 我们来进行演示一下

      • 开始的时候

      • 区间修改 [1, 2]

        • A 的懒惰标记加 1.
          A 的权值加上 1 ∗ 2(加上的数字 × 区间长度).
      • 区间查询 [1, 2]

        • 询问 [1, 2],直接返回 tree[A].
      • 区间查询 [1, 1]

        • A 的懒惰标记下传 (特别注意, A 的懒惰标记和 A 无关, A 的懒惰标记是要加在儿子身上的,而不是自己身上)
          tree[B], tree[C] 加上 lazy[A] ∗ 1(懒惰标记 × 区间长度)
          lazy[B], lazy[C] 加上 lazy[A]
      • 区间查询 [2, 2]

        • 直接返回 tree[C].
    • 区间修改

      • updata

        void updata(int rt, int l, int r, int w) {
            t[rt] += (r - l + 1) * w;//更新权值
            lazy[rt] += w;//更新懒惰标记
        }
        
      • pushdown

        void pushdown(int rt, int l, int r) {
            int mid = (l + r) >> 1;
            updata(rt << 1, l, mid, lazy[rt]);
            //将懒惰标记下传给左儿子
            updata(rt << 1 | 1, mid + 1, r, lazy[rt]);
            //将懒惰标记下传给右儿子
            lazy[rt] = 0;//懒惰标记清零
        }
        
      • 类似之前的询问操作,修改操作也是将区间拆成 (logn) 个区间分别修改,时间复杂度 (O(logn)).

      • code

        void change(int rt, int l, int r) {
            if (x <= l && r <= y) {
                updata(rt, l, r, w);
                return;
            }//区间[x, y]完全覆盖线段树[l, r]
            pushdown(rt, l, r);//下传懒惰标记
            int mid = (l + r) >> 1;
            if (x <= mid) change(rt << 1, l, mid);
            //区间[x, y]与左儿子[l, mid]有交
            if (y > mid) change(rt << 1 | 1, mid + 1, r);
            //区间[x, y]与右儿子[mid + 1, r]有交
            t[rt] = t[rt<<1] + t[rt<<1|1];
        }
        
    • 区间查询

      • 有懒惰标记相比没有懒惰标记的时候,区间查询唯一的不同就是需要下放标记.

      • code

        int sum(int rt, int l, int r) {
            if (x <= l && r <= y) return t[rt];
            int mid = (l + r) >> 1, ans = 0;
            pushdown(rt, l, r);//只有这里不一样
            if (x <= mid) ans += sum(rt << 1, l, mid);
            if (y > mid) ans += sum(rt << 1 | 1, mid + 1, r);
            return ans;
        }
        
    • 例题

      • 线段树模板题
      • code
        #include <cstdio>
        #define int long long 
        /*
        #define self rt, l, r
        #define lson rt << 1, l, mid
        #define rson rt << 1 | 1, mid + 1, r
        */
        //这些宏定义可以让代码更简洁
        using namespace std;
        const int N = 1e5 + 5;
        int n, m, a[N], t[N<<2], x, y, w, lazy[N<<2];
        void build(int rt, int l, int r) {
            if (l == r) { t[rt] = a[l]; return; }
            int mid = (l + r) >> 1;
            build(rt << 1, l, mid);
            build(rt << 1 | 1, mid + 1, r);
            t[rt] = t[rt<<1] + t[rt<<1|1];
        }
        void updata(int rt, int l, int r, int w) {
            t[rt] += (r - l + 1) * w;
            lazy[rt] += w;
        }
        void pushdown(int rt, int l, int r) {
            int mid = (l + r) >> 1;
            updata(rt << 1, l, mid, lazy[rt]);
            updata(rt << 1 | 1, mid + 1, r, lazy[rt]);
            lazy[rt] = 0;
        }
        void add(int rt, int l, int r) {
            if (x <= l && r <= y) {
                updata(rt, l, r, w);
                return;
            }
            pushdown(rt, l, r);
            int mid = (l + r) >> 1;
            if (x <= mid) add(rt << 1, l, mid);
            if (y > mid) add(rt << 1 | 1, mid + 1, r);
            t[rt] = t[rt<<1] + t[rt<<1|1];
        }
        int sum(int rt, int l, int r) {
            if (x <= l && r <= y) return t[rt];
            int mid = (l + r) >> 1, ans = 0;
            pushdown(rt, l, r);
            if (x <= mid) ans += sum(rt << 1, l, mid);
            if (y > mid) ans += sum(rt << 1 | 1, mid + 1, r);
            return ans;
        }
        signed main() {
            scanf("%lld%lld", &n, &m);
            for (int i = 1; i <= n; i++)
                scanf("%d", &a[i]);
            build(1, 1, n);
            while (m--) {
                int t;
                scanf("%lld%lld%lld", &t, &x, &y);
                if (t == 1) scanf("%lld", &w), add(1, 1, n);
                else printf("%lld
        ", sum(1, 1, n));
            }
            return 0;
        }
        

    感谢GSL学长的指导

  • 相关阅读:
    asp.net中插件开发模式说明
    Url路径重写的原理
    Linux上搭建各种环境(一)
    常见Post提交数据方式接口测试
    Jmeter4.0----CSV Data Set Config_使用表格进行参数化(22)
    弱网测试----App
    性能测试基础
    使用fiddler实现手机抓包
    Jmeter4.0----发送邮箱之SMTP Sampler(21)
    Jmeter4.0----发送测试结果到邮箱之邮件观察仪(20)
  • 原文地址:https://www.cnblogs.com/Z8875/p/13199695.html
Copyright © 2011-2022 走看看