1 条题解
-
0
自动搬运
来自洛谷,原作者为

getchar_unlocked
互关条件见主页,关注我可以获得我小号 OIerDb 的关注(需私信) || 最后在线时间:2025年7月3日9时51分搬运于
2025-08-24 21:48:12,当前版本为作者最后更新于2025-03-17 19:55:36,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
树状数组 1 | 树状数组 2(本题) | 修改记录
前置知识
lowbit:
表示 在二进制下,最末尾的 所代表的数字加上后面的 组成的数字。
对于数 做 lowbit 操作,在代码中可以表示为
X&(-X)。例如当 时,其二进制为 ,则 在计算机用反码表示,为 ,将其按位与操作之后得到 ,后面组成的数字为 ,也就是 的值。差分:
一种简单的结构,可以实现 区间修改和 单点查询。
令存储差分数组的数组为 。修改操作,将 增加 ,则需更改 ,;查询操作需要统计前 个数的前缀和。
算法介绍
树状数组,是一种可以实现单点修改和区间查询的数据结构。
而对于本题,变成了区间修改和单点查询,只需要改变原本的数组 的含义变差分即可(与差分模板几乎相同)。
- 对于修改操作,将 增加 ,则需更改 ,;
- 对于查询操作,查询第 个数的值,则需要求前 个 的和。
正确性证明
在这里讲一下树状数组。

上图是一个树状数组的图示。对于每一个区间 ,代表的是这个区间的和。如果想要访问一个区间 ,那么就要从最大的区间开始,逐级划分,然后加和(类似于线段树的思想)。
不难发现,有些区间是没有用的。例如 ,可以用 的值减去 的值得到。图中画叉号的区间都是可以删掉的。

删掉没有用的区间后,给每个区间做一个编号,每一个区间的编号为它的右端点的数字。令编号为 的区间和为 。
你会发现,编号为 的区间应为 。初始化十分好想,可以直接用 的和来表示即可,为:

现在想要查询区间 的和,则需要用 的和减去 的和。想要查询区间 的和,首先要让其加上 ,然后让 向前移动 位,即 。然后再加上 ,再向前移动。循环至 变为 为止,可以保证包含 中的每一个数都被查询,就完成了统计区间 的问题。
由于单次查询,每一次都会去掉二进制末尾的一个 ,在最差情况下会移动 次,故查询的时间复杂度为 。
现在想要修改点 的值,使其增加 。那么相反, 每次需要向后移动 位,即 ,然后修改 。一直向右移动直到移动到大于或等于 的点停止,保证每个包含该点的区间都被修改。
同理,单点修改的时间复杂度亦为 。
整体时间复杂度为 ,空间复杂度 。
注意事项
- 需要开
long long。
代码实现
#include<bits/stdc++.h> using namespace std; const int N=5e5+10; int n,m,a[N]; long long c[N]; // 注意 c 中的值可能超过 int 范围 int lowbit(int x){ return x&(-x); } void add(int x,int k){ // 修改操作 while(x<=n){ c[x]+=k; x+=lowbit(x); } return; } long long sum(int x){ // 查询操作 long long res=0; while(x){ res+=c[x]; x-=lowbit(x); } return res; } int main(){ cin>>n>>m; for(int i=1;i<=n;++i){ cin>>a[i]; add(i,a[i]-a[i-1]); // 按照差分含义初始化 } while(m--){ int op;cin>>op; if(op==1){ int l,r,k;cin>>l>>r>>k; add(l,k),add(r+1,-k); // 差分操作 } else{ int x;cin>>x; cout<<sum(x)<<"\n"; // 前 x 个数的和 } } return 0; }
- 1
信息
- ID
- 1851
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 4
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者