zoukankan      html  css  js  c++  java
  • P3372 【模板】线段树 1

    线段树模板题

    这个题的题面十分好理解,不再进行过多解释。

    做题思路(数组)

    建树与维护

    建树代码

    void build(int l, int r, int & rt) {//递归建树
        rt = ++tot;
        tr[rt].l = l, tr[rt].r = r; 
        if (l == r) { 
            scanf("%lld", &tr[rt].v);
            return;
        }
        int mid = (l + r) >> 1; 
        build(l, mid, tr[rt].lc);
        build(mid + 1, r, tr[rt].rc); 
        push_up(tr[rt].lc, tr[rt].rc, rt); //对信息进行维护
    }
    

    维护代码

    void push_up(int lc, int rc, int rt) { 
        tr[rt].v = tr[lc].v + tr[rc].v;
    }
    

    区间修改实现

    void push_down(int lc, int rc, int rt) { 
        tr[lc].tag += tr[rt].tag; 
        tr[rc].tag += tr[rt].tag;
        tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和
        tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag;
        tr[rt].tag = 0; 
    }
    
    void up_date(int l, int r, int rt) {
        if (x <= l && y >= r) { 
            tr[rt].tag += k; 
            tr[rt].v += (r - l + 1) * k; 
            return;
        }
        push_down(tr[rt].lc, tr[rt].rc, rt); 
        int mid = (l + r) >> 1;
        if (x <= mid)
            up_date(l, mid, tr[rt].lc);
        if (y > mid)
            up_date(mid + 1, r, tr[rt].rc); 
        push_up(tr[rt].lc, tr[rt].rc, rt); 
    }
    
    long long get_ans(int l, int r, int rt) {
        if (x <= l && y >= r) 
            return tr[rt].v; 
        push_down(tr[rt].lc, tr[rt].rc, rt); 
        ll ans = 0;
        int mid = (l + r) >> 1;
        if (x <= mid)
            ans += get_ans(l, mid, tr[rt].lc);
        if (y > mid)
            ans += get_ans(mid + 1, r, tr[rt].rc);
        return ans;
    }
    

    代码实现(数组)

    #include <cstdio>
    typedef long long int ll;
    using namespace std;
    #define N 200100 
    int n, m, root, tot, p, x, y;
    ll k;
    struct node {
        int l, r, lc, rc; 
        long long v, tag; 
    } tr[N];
    
    void push_up(int lc, int rc, int rt) { 
        tr[rt].v = tr[lc].v + tr[rc].v;
    }
    
    void push_down(int lc, int rc, int rt) { 
        tr[lc].tag += tr[rt].tag; 
        tr[rc].tag += tr[rt].tag;
        tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和
        tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag;
        tr[rt].tag = 0; 
    }
    
    void build(int l, int r, int & rt) {
        rt = ++tot;
        tr[rt].l = l, tr[rt].r = r; 
        if (l == r) { 
            scanf("%lld", &tr[rt].v);
            return;
        }
        int mid = (l + r) >> 1; 
        build(l, mid, tr[rt].lc);
        build(mid + 1, r, tr[rt].rc); 
        push_up(tr[rt].lc, tr[rt].rc, rt); 
    }
    
    void up_date(int l, int r, int rt) {
        if (x <= l && y >= r) { 
            tr[rt].tag += k; 
            tr[rt].v += (r - l + 1) * k; 
            return;
        }
        push_down(tr[rt].lc, tr[rt].rc, rt); 
        int mid = (l + r) >> 1;
        if (x <= mid)
            up_date(l, mid, tr[rt].lc);
        if (y > mid)
            up_date(mid + 1, r, tr[rt].rc); 
        push_up(tr[rt].lc, tr[rt].rc, rt); 
    }
    
    ll get_ans(int l, int r, int rt) {
        if (x <= l && y >= r) 
            return tr[rt].v; 
        push_down(tr[rt].lc, tr[rt].rc, rt); 
        ll ans = 0;
        int mid = (l + r) >> 1;
        if (x <= mid)
            ans += get_ans(l, mid, tr[rt].lc);
        if (y > mid)
            ans += get_ans(mid + 1, r, tr[rt].rc);
        return ans;
    }
    
    int main() {
        scanf("%d %d", &n, &m);
        build(1, n, root);
        while (m--) {
            scanf("%d %d %d", &p, &x, &y);
            if (p == 1) {
                scanf("%lld", &k);
                up_date(1, n, 1);
            } else
                printf("%lld
    ", get_ans(1, n, 1));
        }
        return 0;
    }
    

    下面是指针实现

    虽然使用指针来写线段树的人很少,但是个人觉得使用指针写线段树将更加容易理解。

    做题思路(指针)

    定义结构体

    struct Node {
      ll tag, v;//延迟标记,子节点值 
      int l, r;//左端点,右端点 
      Node *ls, *rs;//左孩子、右孩子 
    }
    

    建树

    Node(const int L, const int R) {//构造函数 
        l = L; r = R; //当前结点左右端点 
        if (l == r) {//叶子结点 
          tag = 0;//清空延迟标记 
          v = a[l];//其值等于这个结点对应的值本身 
          ls = rs = NULL;//没有左右孩子结点 
        } else {
          tag = 0;
          int M = (L + R) >> 1;//取中点 
          ls = new Node(L, M);//递归向下一层进行构造 
          rs = new Node(M + 1, R);
          pushup();//值等于左右孩子结点值的和 
        }
      }
    

    维护信息

    inline void pushup() {//上传信息 
        v = ls->v + rs->v;//当前结点值=左结点值+右结点值 
      }
    

    区间修改的实现

      inline void maketag(const ll w) {//给一个结点打延迟标记 
        v += (r - l + 1) * w;//更新当前结点值 
        tag += w;//延迟标记 
      }
      inline void pushdown() {//下传延迟标记 
        if (tag == 0) return;//当前结点没有延迟标记,直接返回 
        ls->maketag(tag);//向左右孩子打标记 
        rs->maketag(tag);
        tag = 0;//清空当前结点延迟标记 
      }
      inline bool InRange(const int L, const int R) { return (L <= l) && (r <= R); }
      //一个结点被完全包含 
      inline bool OutofRange(const int L, const int R) { return  (l > R) || (r < L); }
      //一个结点完全没有重叠(毫不相关) 
      
      void upd(const int L, const int R, const ll w) {//赋值操作 
        if (InRange(L, R)) {//完全包含 
          maketag(w);//先打上延迟标记 
        } else if (!OutofRange(L, R)) {//有重叠部分但没有完全包含 
          pushdown();//下传延迟标记到子节点 
          ls->upd(L, R, w);//递归更改信息 
          rs->upd(L, R, w); 
          pushup();//向上传递信息 
        } 
      }
      
      ll qry(const  int L, const int R) {//询问一段区间的和 
        if (InRange(L, R)) return v;//完全包含直接返回这个结点的值 
        if (OutofRange(L, R)) return 0;//毫不相关,返回0 
        pushdown();//下传 
        return ls->qry(L, R) + rs->qry(L, R);//递归询问 
      }
    

    代码实现(指针)

    #include <cstdio>
    
    const int maxn = 100005;
    
    typedef long long int ll;
    
    int n, q;
    ll a[maxn];//原数组 
    
    struct Node {
      ll tag, v;//延迟标记,子节点值 
      int l, r;//左端点,右端点 
      Node *ls, *rs;//左孩子、右孩子 
      
      inline void maketag(const ll w) {//给一个结点打延迟标记 
        v += (r - l + 1) * w;//更新当前结点值 
        tag += w;//延迟标记 
      }
      inline void pushup() {//上传信息 
        v = ls->v + rs->v;//当前结点值=左结点值+右结点值 
      }
      inline void pushdown() {//下传延迟标记 
        if (tag == 0) return;//当前结点没有延迟标记,直接返回 
        ls->maketag(tag);//向左右孩子打标记 
        rs->maketag(tag);
        tag = 0;//清空当前结点延迟标记 
      }
      
      Node(const int L, const int R) {//构造函数 
        l = L; r = R; //当前结点左右端点 
        if (l == r) {//叶子结点 
          tag = 0;//清空延迟标记 
          v = a[l];//其值等于这个结点对应的值本身 
          ls = rs = NULL;//没有左右孩子结点 
        } else {
          tag = 0;
          int M = (L + R) >> 1;//取中点 
          ls = new Node(L, M);//递归向下一层进行构造 
          rs = new Node(M + 1, R);
          pushup();//值等于左右孩子结点值的和 
        }
      }
      
      // this l, r
      inline bool InRange(const int L, const int R) { return (L <= l) && (r <= R); }
      //一个结点被完全包含 
      inline bool OutofRange(const int L, const int R) { return  (l > R) || (r < L); }
      //一个结点完全没有重叠(毫不相关) 
      
      void upd(const int L, const int R, const ll w) {//赋值操作 
        if (InRange(L, R)) {//完全包含 
          maketag(w);//先打上延迟标记 
        } else if (!OutofRange(L, R)) {//有重叠部分但没有完全包含 
          pushdown();//下传延迟标记到子节点 
          ls->upd(L, R, w);//递归更改信息 
          rs->upd(L, R, w); 
          pushup();//向上传递信息 
        } 
      }
      
      ll qry(const  int L, const int R) {//询问一段区间的和 
        if (InRange(L, R)) return v;//完全包含直接返回这个结点的值 
        if (OutofRange(L, R)) return 0;//毫不相关,返回0 
        pushdown();//下传 
        return ls->qry(L, R) + rs->qry(L, R);//递归询问 
      }
    };
    
    int main() {
      scanf("%d%d", &n, &q);
      for (int i = 1; i <= n; ++i) scanf("%lld", a + i);
      Node *rot = new Node(1, n);
      for (ll o, x, y, z; q; --q) {
        scanf("%lld%lld%lld", &o, &x, &y);
        if (o == 1) {
          scanf("%lld", &z);
          rot->upd(x, y, z); 
        } else {
          printf("%lld
    ", rot->qry(x, y));
        }
      }
      return 0;
    }
    

    鸣谢

    感谢zay大佬讲解线段树的指针实现,zay Orz !

  • 相关阅读:
    list和set的区别
    day13
    11期
    接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?
    Object类?
    swith的用法和注意事项?
    Ioc和DI的区别?
    多态的好处?
    抽象和接口的区别?
    内部类,匿名内部类?
  • 原文地址:https://www.cnblogs.com/Kyriech-Francis/p/Answer_P3372.html
Copyright © 2011-2022 走看看