带懒标记的线段树模板

模板_ 线段树

带懒标记的线段树

性质

线段树是运用分块思想的树形结构。其主要的作用是维护区间信息的数据结构。
线段树可以在 \(O(\text{log}(n))\)的时间复杂度内完成单点修改、区间修改、区间查询等操作。
值得注意的是线段树能够维护的信息需要满足可加性。即例如\(max(a,b,c)=max(max(a,b),c)=max(max(a,c),b)=max(max(b,c),a)\)满足可加性,而\((a\mod b \mod c )\neq( a\mod c \mod b)\)不满足可加性。

可加性是指对于某种变换来说,特定的“加法”和该变换的顺序可颠倒而不影响结果,这样一种性质。——来自wiki百科


操作

符号 含义
a 原数组
d 线段树节点维护值
lazytag 线段树节点的懒标记值
p 当前的节点
s 查询区间的开始
e 查询区间的结尾
l 节点区间的开始
r 节点区间的结尾

一般习惯:

  • 建树从下标为1开始
  • mid = l + r >> 1

建立线段树

\(a=\{1,2,3\}\)为例子,树的结构如下:

首先是提供的参数void build(int s, int e, int p)
然后是伪代码:

void build(int s, int e, int p){
    if(s == e){
    //当前d[p]是叶子节点
        d[p]=a[p];
        return;
    }
    int m = s + e >> 1;
    build(左子树[s,m]);
    build(左子树[m+1,e]);
    pushup(p);
}

即为:对于一个节点,我们从根节点开始,递归的构建整个树到叶子节点,并且从叶子节点\(\text{pushup}\)到根节点。

对于一个节点:

  • 若当前的节点是叶子节点,即s==e,那么\(d[p]\)(对应区间\([s,s]\))的就是\(a[s]\)
  • 若当前的节点是非叶子节点,即s<e,那么将区间分割为两个部分即\([s,m]\)(对应下标为\(p<<1\))和\([m+1,e]\)(对应的下标为\(p<<1|1\)),其中\(m=s+e>>1\),递归到左右儿子进行建树。
void build(ll s, ll e, ll p) {
  //[s,e]建立线段树 根为p
  if (s == e) {
    d[p] = a[s];
    return;
  }
  ll m = (s + e) >> 1;
  build(s, m, p << 1), build(m + 1, e, (p << 1) | 1);
  d[p] = d[p << 1] + d[(p << 1) | 1];
}

区间查询

对于一个节点,其存储的是\([l,r]\)区间内的维护的值,而对于一个区间查询\([s,e]\)
若有查询的区间\([s,e]\)完全覆盖当前的节点区间\([l,r]\),即s<=l && r<=e查询的值即为\(d[p]\)

否则,将区间一分为二

  • \([l,m]\)左儿子对应下标为\(p<<1\)
  • \([m+1,r]\)右儿子对应的下标为\(p<<1|1\)
    此时递归到左右儿子子树进行查询

如果每一次更新区间值,都会使得整个线段树向下更新到根节点。在区间更新的时候应该使用懒标记的线段树,延迟整个节点的信息更新。

带有懒标记的线段树,实际上是父节点暂时记录了下推到子节点的信息。在查询时,才将延迟更新的节点信息加载到子节点。

对于参数\(ll getsum(ll l, ll r, ll s, ll e, ll p)\)

ll getsum(ll l, ll r, ll s, ll e, ll p) {
    if(当前区间是叶节点) return d[p]
    ll m = l + r >> 1;
    if(lazy[p]){//当前的节点是带有延迟更新的信息的
    //pushdown
        d[p的左儿子] += lazy[p] * (p左儿子区间长度);
        d[p的右儿子] += lazy[p] * (p右儿子区间长度);
        lazy[p的左儿子] += lazy[p];
        lazy[p的右儿子] += lazy[p];
    }
    lazy[p] = 0;//还原懒惰标记
    ll sum = 0;//查询值
    if(左儿子存在) sum += getsum(l,r,左儿子区间,左儿子下标);
    if(右儿子存在) sum += getsum(l,r,右儿子区间,右儿子下标);
    return sum;
}

这里为了将清楚流程,借用oi-wiki的图片:

segt6

segt7
segt8

segt9

segt10

此时如果当前的区间进行查询[1,1](即为B节点):
那么A的信息将会传递给B,这个过程即为\(pushdown\)过程。
segt12

segt13

ll getsum(ll l, ll r, ll s, ll e, ll p) {
  /*
    [l,r]当前查询的区间
    [s,e]当前节点的区间
    p 为当前的节点编号
  */
  if (l <= s && e <= r) return d[p];
  ll m = (s + e) / 2, sum = 0;
  //更新子节点和传递懒标记
  if (lazy[p]){
    d[p << 1] += lazy[p] * (m - s + 1), d[p << 1 | 1] += lazy[p] * (e - m);
    lazy[p << 1] += lazy[p], lazy[p << 1 | 1] += lazy[p];
  }
  lazy[p] = 0;  //还原懒标记
  if (l <= m) sum += getsum(l, r, s, m, p << 1);
  if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
  return sum;
}

区间更新

综上所述,区间修改(更新)过程是产生新的懒标记,而区间查询是将懒标记向下传递\(pushdown\)

函数参数void update(ll l, ll r, ll c, ll s, ll e, ll p)
伪码描述:

void update(ll l, ll r, ll c, ll s, ll e, ll p) {
    if(当前的查询区间完全包裹节点) {
        d[p] += 当前节点区间长度 * 修改的值c;//即把区间内的所有点都加上c,等效于这个区间加上了len*c
        lazy[p] += c;//区间更新的值向下传递
        return ;
    }
    ll m = l + r >> 1;
    if(lazy[p]){//当前的节点是带有延迟更新的信息的
    //pushdown(p);
        d[p的左儿子] += lazy[p] * (p左儿子区间长度);
        d[p的右儿子] += lazy[p] * (p右儿子区间长度);
        lazy[p的左儿子] += lazy[p];
        lazy[p的右儿子] += lazy[p];
    }
    if(左儿子存在) update(l,r,左儿子区间,左儿子下标);
    if(右儿子存在) update(l,r,右儿子区间,右儿子下标);
    pushup(p);
}

即为

void update(ll l, ll r, ll c, ll s, ll e, ll p) {
  /*
    带有懒标记的更新操作
    [l,r]当前查询的区间
    [s,e]当前节点的区间
    c维护的信息
    p节点位置
  */
  if (l <= s && e <= r) {
    d[p] += (e - s + 1) * c;
    lazy[p] += c;
    return;
  }
  ll m = (s + e) >> 1;
  //叶子节点不需要下放懒标记
  if (lazy[p]) {  //当前的节点懒标记可以向下传递
    //左儿子和右儿子更新节点信息
    d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
    lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
    lazy[p] = 0;  //消除懒标记
  }
  if (l <= m) update(l, r, c, s, m, p << 1);
  if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
  d[p] = d[p << 1] + d[(p << 1) | 1];
}

pushup

该部分操作是递归到儿子时,回溯地影响自己的父亲节点,是自底向上的修改。
涉及的操作是buildupdate
本质是更新了子节点后,重新计算父节点的信息。

操作为

d[p] = d[p << 1] + d[(p << 1) | 1];

pushdown

该部分操作是把父亲欠下儿子的信息向下返还,用懒惰标记更新当前的维护值。
涉及的操作是getsumupdate

  if (lazy[p]) {  //当前的节点懒标记可以向下传递
//左儿子和右儿子更新节点信息
    d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
    lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
    lazy[p] = 0;  //消除懒标记
}

复杂度

\(O(\text{log}(n))\)的时间复杂度内完成单点修改、区间修改、区间查询等操作

空间复杂度较高,上限是\(O(9*n)\)(在默认开4倍的空间的情况下,注意!在1e5容易爆)
空间的复杂度上限为\(O(2^{\lceil{\text{log}(n)+1} \rceil })\),但是实际竞赛中一般取\(4*\text{maxn}\)

模板

P3372洛谷 线段树模板1

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
static int faster_iostream = []() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(NULL);
  return 0;
}();
const int maxn = 1e5 + 10;
const int maxx = (int)ceil(log(1e5+10)/log(2))*1.2;//270000
/*a是原数组,d是线段树维护的信息,b是懒标记*/
/*
1. 懒标记b>0说明当前的节点欠儿子某些信息
pushdown在更新懒标记节点的时候,还要向下传递
更新其儿子的懒标记信息
2. 同时也更新儿子维护的信息d(信息的一半)
*/
// 262144 == 1 << ((int)ceil(log(1e5 + 10) / log(2)) + 1);
ll a[maxn], d[262144], lazy[262144];
void build(ll s, ll e, ll p) {
  //[s,e]建立线段树 根为p
  if (s == e) {
    d[p] = a[s];
    return;
  }
  ll m = (s + e) >> 1;
  build(s, m, p << 1), build(m + 1, e, (p << 1) | 1);
  d[p] = d[p << 1] + d[(p << 1) | 1];
}
ll getsum(ll l, ll r, ll s, ll e, ll p) {
  /*
    [l,r]当前查询的区间
    [s,e]当前节点的区间
    p 为当前的节点编号
  */
  if (l <= s && e <= r) return d[p];
  ll m = (s + e) / 2, sum = 0;
  //更新子节点和传递懒标记
  if (lazy[p])
    d[p << 1] += lazy[p] * (m - s + 1), d[p << 1 | 1] += lazy[p] * (e - m),
        lazy[p << 1] += lazy[p], lazy[p << 1 | 1] += lazy[p];
  lazy[p] = 0;  //还原懒标记
  if (l <= m) sum += getsum(l, r, s, m, p << 1);
  if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
  return sum;
}

void update(ll l, ll r, ll c, ll s, ll e, ll p) {
  /*
    带有懒标记的更新操作
    [l,r]当前查询的区间
    [s,e]当前节点的区间
    c维护的信息
    p节点位置
  */
  if (l <= s && e <= r) {
    d[p] += (e - s + 1) * c;
    lazy[p] += c;
    return;
  }
  ll m = (s + e) >> 1;
  //叶子节点不需要下放懒标记
  if (lazy[p]) {  //当前的节点懒标记可以向下传递
    //左儿子和右儿子更新节点信息
    d[p << 1] += lazy[p] * (m - s + 1), d[(p << 1) | 1] += lazy[p] * (e - m);
    lazy[p << 1] += lazy[p], lazy[(p << 1) | 1] += lazy[p];
    lazy[p] = 0;  //消除懒标记
  }
  if (l <= m) update(l, r, c, s, m, p << 1);
  if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
  d[p] = d[p << 1] + d[(p << 1) | 1];
}
ll n, q;
int main() {
  cin >> n >> q;
  for (int i = 1; i <= n; i++) cin >> a[i];
  build(1, n, 1);
  ll a, b, c, d;
  while (q--) {
    cin >> a >> b >> c;
    if (a == 2) {
      cout << getsum(b, c, 1, n, 1) << endl;
    } else {
      cin >> d;
      update(b, c, d, 1, n, 1);
    }
  }
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll a[maxn], lazy[300000], d[300000];
void pushup(ll p) { d[p] = d[p << 1] + d[p << 1 | 1]; }
void pushdown(ll p, ll s, ll m, ll e) {
  d[p << 1] += lazy[p] * (m - s + 1);
  d[p << 1 | 1] += lazy[p] * (e - m);
  lazy[p << 1] += lazy[p];
  lazy[p << 1 | 1] += lazy[p];
  lazy[p] = 0;
}
void build(ll s, ll e, ll p) {
  if (s == e) {
    d[p] = a[s];
    return;
  }
  ll m = s + e >> 1;
  build(s, m, p << 1);
  build(m + 1, e, (p << 1) | 1);
  pushup(p);
}
ll getsum(ll l, ll r, ll s, ll e, ll p) {
  if (l <= s && r >= e) return d[p];
  ll m = (s + e) >> 1;
  if (lazy[p]) {
    pushdown(p, s, m, e);
  }
  ll sum = 0;
  if (l <= m) sum += getsum(l, r, s, m, p << 1);
  if (r > m) sum += getsum(l, r, m + 1, e, p << 1 | 1);
  return sum;
}
void update(ll l, ll r, ll s, ll e, ll c, ll p) {
  if (l <= s && r >= e) {
    d[p] += (e - s + 1) * c;
    lazy[p] += c;
    return;
  }
  ll m = (s + e) >> 1;
  if (lazy[p]) {
    pushdown(p, s, m, e);
  }
  if (l <= m) update(l, r, s, m, c, p << 1);
  if (m < r) update(l, r, m + 1, e, c, p << 1 | 1);
  pushup(p);
}
ll m, n;
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) cin >> a[i];
  build(1, n, 1);
  ll a, b, c, d;
  while (m--) {
    cin >> a >> b >> c;
    if (a == 2) {
      //查询bc
      cout << getsum(b, c, 1, n, 1) << endl;
    } else {
      cin >> d;
      update(b, c, 1, n, d, 1);
    }
  }
  return 0;
}
posted @ 2020-03-09 21:24  AdaMeta730  阅读(553)  评论(1)    收藏  举报