区间修改、区间求和的树状数组做法

文章介绍了如何使用线段树数据结构来处理区间修改和区间求和的问题,通过建立两棵树存储差分数组的值和乘以索引的值,从而实现区间操作。文章提到了Python可能因数据规模限制无法通过所有测试,而C++则可以胜任。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k。
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 或 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间[x,y] 内每个数加上 k。
  2. 2 x y:输出区间[x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例

输入

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

输出 

11
8
20

说明/提示

对于 30%的数据:n≤8,m≤10。
对于 70% 的数据:n≤10^3,m≤10^4。
对于 100% 的数据:1≤n,m≤10^5。

保证任意时刻数列中所有元素的绝对值之和 ≤1018。

【样例解释】

首先我声明一下:这题是线段树的模板题,因为树状数组是单点修改、区间求和的数据结构。在差分数组(也是单点修改、区间求和的数据结构)的加持下树状数组可以实现区间修改、单点求和的功能。

那么怎么才能实现从区间修改、单点求和转化为我们要的区间修改区间求和呢?这需要一点数学知识。

假设原数组是a,差分数组是d,那么

求a的前缀和:\large \sum ^n_{i=1}a[i] = \sum^n_{i=1}\sum^i_{j=1}d[j]

然后我们以d出现的次数为基础重新排列,易得d[1]出现n次,d[2]出现n-1次……

则原式=   \large \sum ^n_{i=1}d[i]*(n-i+1)=(n+1)\sum^n_{i+1}d[i]-\sum^n_{i=1}d[i]*i

有一点要注意,因为数据是在不断更新着,并且树状数组来存d[i]和存d[i]*i并没有直接的倍数关系,所以我们要建两棵树tree1和tree2来分别d[i]和d[i]*i。

ps. python因自身特性而无法通过全部数据,经检测C++可以通过,时间复杂度没有问题。

# 前三个函数均为树状数组模板,但是修改和求值因为这题两树的特性而略有变动
def low_bit(x):
    return x & -x


def add(x, k1, k2):  # 单点修改
    while x <= n:
        tree1[x] += k1
        tree2[x] += k2
        x += low_bit(x)


def s(x):  # 区间求值
    x0 = x
    ans1 = ans2 = 0
    while x:
        ans1 += tree1[x]  # 分别累计两树的区间和
        ans2 += tree2[x]
        x -= low_bit(x)
    return (x0+1)*ans1-ans2


n, m = map(int, input().split())
a = [0]+list(map(int, input().split()))
tree1 = [0]*(n+1)
tree2 = [0]*(n+1)
for i in range(1, n+1):
    add(i, a[i]-a[i-1], i*(a[i]-a[i-1]))  # 建树的过程和更新并没有区别,注意两树更新的值是不一样的
for _ in range(m):
    a = list(map(int, input().split()))
    x, y = a[1], a[2]
    if a[0] == 1:
        k1 = a[3]
        add(x, k1, x*k1)
        if y < n:  # 如果y已经到最后一位了,就只需要更新d[x]
            add(y+1, -k1, -(y+1)*k1)
    else:
        print(s(y)-s(x-1))  # 树状数组通过相减实现区间和的询问

看我写得这么辛苦点个赞吧Orz

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈子昂-北工大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值