带简单懒标记的线段树

带简单懒标记的线段树

学习过线段树后,才可以理解带懒标记的线段树,下面这题比较基础,可以直接阅读代码的来进行理解,注释写的比较完善

题目:P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct node//懒标记结构体
{
     ll number;//记录线段树数组中的元素所管理的区间的区间和
     ll lazy;//懒标记,记录其所管理的区间的每一个值要加上多少
     node() : number(0), lazy(0) {}
} tree[6000000];
ll a[1000000] = {0};//原数组

void build(ll x, ll l, ll r)//建树操作和没有加懒标记的一样。参数意义是:在线段树下标为x的元素,其管理的范围是[l,r]
{
     if (l == r)
     {
          tree[x].number = a[l];
          return;
     }

     ll m = (l + r) / 2;
     build(x * 2, l, m);
     build(x * 2 + 1, m + 1, r);
     tree[x].number = tree[x * 2].number + tree[x * 2 + 1].number;
}

void spread(ll x, ll l, ll r)//这个函数是用来处理懒标记下放的,也就是当这个需要下放的这个节点的懒标记不为空,那么他的每一个子树所管理的范围[l,r](即原数组下标从 l 到 r 的元素)里面的值都要加上懒标记的值lazy
{
     if (tree[x].lazy != 0)
     {
          //修改维护额度左右儿子的值
          ll m = (l + r) / 2;
          tree[x * 2].number += (tree[x].lazy * (m - l + 1));//对于左子树所管理的区间和更新,因为左子树管理的范围的每一个点都会加上lazy,因此其number值的更新应该是lazy * 范围
          tree[x * 2].lazy += tree[x].lazy;//并更新左子树的lazy标记
          tree[x * 2 + 1].number += (tree[x].lazy * (r - (m + 1) + 1));//更新右子树
          tree[x * 2 + 1].lazy += tree[x].lazy;

          //注意下传后这个节点的懒标记应该清零
          tree[x].lazy = 0;
     }
}

void upd(ll p1, ll p2, ll v, ll x, ll l, ll r)//更新原数组下标为[p1,p2]里的每个元素都加上v
{
     if (l >= p1 && r <= p2) //当管理的区间全部都要修改的时候才加上懒标记并更新节点的值
     {
          tree[x].number += (v * (r - l + 1));
          tree[x].lazy += v;
          return;
     }

     spread(x, l, r);//当这个节点所管理的区间不是全部都要修改的时候,就不能在这个节点的懒标记加上新的值了,因为这样的话原数组那些不需要被修改的元素也会被误加。因此要下放懒标记,直到某个节点的所管理的区间全部需要修改为止

     //若下标为x的线段树节点管理的范围里并不是每一个都需要修改,那么这个时候就需要下放懒标记
     ll m = (l + r) / 2;
     if (p1 <= m)
          upd(p1, p2, v, x * 2, l, m);
     if (p2 > m)//注意这里不是else if,因为如果p1、p2横跨l、r,那么右子树也是需要改懒标记的
          upd(p1, p2, v, x * 2 + 1, m + 1, r);

     tree[x].number = tree[x * 2].number + tree[x * 2 + 1].number;
}

ll ask(ll x, ll l, ll r, ll p1, ll p2)
{
     if (l >= p1 && r <= p2)
          return tree[x].number;//懒标记只是要加在子树上的值,而子树总体会加上多少已经算在number里了

     spread(x, l, r);

     ll m = (l + r) / 2;
     if (p2 <= m)
          return ask(x * 2, l, m, p1, p2);
     else if (p1 > m)
          return ask(x * 2 + 1, m + 1, r, p1, p2);

     return ask(x * 2, l, m, p1, m) + ask(x * 2 + 1, m + 1, r, m + 1, p2);
}

int main()
{
     ll n, m;
     cin >> n >> m;
     for (ll i = 1; i <= n; i++)
          cin >> a[i];
     build(1, 1, n);
     for (ll i = 0; i < m; i++)
     {
          int choice;
          cin >> choice;
          if (choice == 1)
          {
               ll x, y, k;
               cin >> x >> y >> k;
               upd(x, y, k, 1, 1, n);
          }
          else
          {
               ll x, y;
               cin >> x >> y;
               cout << ask(1, 1, n, x, y) << endl;
          }
     }
     return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值