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 !

原文地址:https://www.cnblogs.com/Kyriech-Francis/p/Answer_P3372.html