线段树常用案例1——区间求和

本篇博客在上一篇博客基础上讲解,链接:线段树

引例

来看一道题目:A Simple Problem with Integers

这道题的题意是给定一个大小为N的数组,现在每次对数组进行以下两种操作中的一种:

  • 如果是Q,输入a,b,则求区间[a,b]的元素和
  • 如果是C,输入a,b,c,则对区间[a,b]中的每个元素进行加c操作

首先如果只是区间求和查询操作比较好处理,在构造线段树的时候预先求和即可,但题目关键在于对于一段区间的元素进行更新之后的区间求和

如果我们每次的更新操作都处理到叶子结点,那么所花费的代价是不可接受的。例如对于一个区间[1,10],每次都对整个区间进行加1操作,然后再求某个区间的和,那么每次的更新操作都相当于一次线段树的构造操作,在O(n)时间内才能处理完,显然极其费时。

如果我们每次的更新操作不处理到叶子结点,具体见下图,第一次更新区间[1,3]内每个元素加4的操作,如果接下来要求的区间[3,3]的和,那么由于没有向下更新,[3,3]区间的和必然为0。

 由以上总结,我们需要一种 根据情况进行向下更新的 方法,由此引入lazy-tag处理区间求和的问题。


lazy-tag在区间求和中的应用

lazy-tag顾名思义,就是懒惰标记,这像是上面两种方法的折中:更新只在可能需要的时候更新。

下面贴出代码,来分析一下懒惰标记的应用。

void update(int x, int y, int c, int num)
{
  if(T[num].left == x && T[num].right == y)
  {
    T[num].add += c;
    T[num].sum += c * (T[num].right - T[num].left + 1);
    return ;
  }
  if(T[num].add)
  {
    T[2*num].add += T[num].add;
    T[2*num].sum += T[num].add * (T[2*num].right - T[2*num].left + 1);
    T[2*num+1].add += T[num].add;
    T[2*num+1].sum += T[num].add * (T[2*num+1].right - T[2*num+1].left + 1);
    T[num].add = 0;
  }

  int mid = (T[num].left + T[num].right) / 2;
  if(x > mid) update(x, y, c, 2*num+1);
  else if(y <= mid) update(x, y, c, 2*num);
  else
  {
    update(x, mid, c, 2*num);
    update(mid+1, y, c, 2*num+1);
  }
  T[num].sum = T[2*num].sum + T[2*num+1].sum;
}

首先讲讲update的参数,[x,y]表示想要更新的区间,c表示对于区间内的所有元素进行加c操作。

我们可以发现,函数里有两个选择语句,如果本次递归中当前的线段树区间和想要更新的区间正好相等,那么更新这个区间的和,并且给这个结点打上懒惰标记;否则说明更新的区间被线段树的区间所包含,因此如果该结点有懒惰标记应该立即往下传,传给它的左右子树,并且更新左右子树的和,同时将该结点的懒惰标记清零。

就拿上面的图片来举例子。如果本次更新区间[1,3]中每次元素都加4,就相当于在[1,3]这个结点打了懒惰标记,那么它起作用的时机就在于下次查询,例如查询区间[3,3]的时候,首先应该检查[1,3]这个结点有没有懒惰标记,如果有应该往下传,这样才能保证查询的结果是正确的。

因此从上面就可以看出 懒惰标记更新只在可能需要的时候更新 的意思。 当前的区间如果有懒惰标记,而查询的区间是当前区间的子区间,那么很可能这次查询需要更新之后才有正确的结果,因此懒惰标记应该立即传给该区间的左右子树。


完整代码

贴出引例中的完整代码,需要注意该题懒惰标记在区间求和的时候会爆int,因此用long long。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define maxn 1000010
using namespace std;
struct node
{
  int left;
  int right;
  long long add;
  long long sum;
}T[maxn*4];
long long ans;
long long val[maxn];

void build(int left, int right, int num)
{
  T[num].left = left;
  T[num].right = right;
  T[num].add = 0;
  if(T[num].left == T[num].right)
  {
    T[num].sum = val[left];
    return;
  }

  int mid = (left + right) / 2;
  build(left, mid, 2*num);
  build(mid+1, right, 2*num+1);

  T[num].sum = T[2*num].sum + T[2*num+1].sum;
}
void query(int x, int y, int num)
{

  if(T[num].left == x && T[num].right == y)
  {
    ans += T[num].sum;
    return ;
  }
  if(T[num].add)
  {
    T[2*num].add += T[num].add;
    T[2*num].sum += T[num].add * (T[2*num].right - T[2*num].left + 1);
    T[2*num+1].add += T[num].add;
    T[2*num+1].sum += T[num].add * (T[2*num+1].right - T[2*num+1].left + 1);
    T[num].add = 0;
  }

  int mid = (T[num].left + T[num].right) / 2;

  if(x > mid) query(x, y, 2*num+1);
  else if(y <= mid) query(x, y, 2*num);
  else
  {
    query(x, mid, 2*num);
    query(mid+1, y, 2*num+1);
  }
}
void update(int x, int y, int c, int num)
{
  if(T[num].left == x && T[num].right == y)
  {
    T[num].add += c;
    T[num].sum += c * (T[num].right - T[num].left + 1);
    return ;
  }
  if(T[num].add)
  {
    T[2*num].add += T[num].add;
    T[2*num].sum += T[num].add * (T[2*num].right - T[2*num].left + 1);
    T[2*num+1].add += T[num].add;
    T[2*num+1].sum += T[num].add * (T[2*num+1].right - T[2*num+1].left + 1);
    T[num].add = 0;
  }

  int mid = (T[num].left + T[num].right) / 2;
  if(x > mid) update(x, y, c, 2*num+1);
  else if(y <= mid) update(x, y, c, 2*num);
  else
  {
    update(x, mid, c, 2*num);
    update(mid+1, y, c, 2*num+1);
  }
  T[num].sum = T[2*num].sum + T[2*num+1].sum;
}
int main()
{

  int N, Q;
  while(~scanf("%d%d", &N, &Q))
  {
    for(int i = 1; i <= N; i++) scanf("%lld", &val[i]);
    build(1, N, 1);
    for(int i = 1; i <= Q; i++)
    {
      char str[5];
      int a, b;
      scanf("%s%d%d", &str, &a, &b);

      if(!strcmp(str, "Q"))
      {
        ans = 0;
        query(a, b, 1);
        printf("%lld\n", ans);
      }
      else
      {
        int c;
        scanf("%d", &c);
        update(a, b, c, 1);
      }
    }
  }

  return 0;
}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值