P3374 【模板】树状数组 1

记录117

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
int a[MAXN],c[MAXN];// a[] 是原始数组,c[] 是树状数组(核心数组)
int n,m; // n 是数列长度,m 是操作次数
int lowbit(int x){//计算 x 的二进制表示中最低位的 1 所代表的值 
	return x&(-x);
}//用途:用于确定当前节点管辖的范围,以及在树中向上/向下跳转 
int sum(int x){// sum 函数:查询前缀和
	int res=0;
	for(int i=x;i>0;i-=lowbit(i)) res+=c[i];
	return res;
}// 作用:计算原数组 a 中区间 [1, x] 的元素之和
void add(int x,int y){// add 函数:单点修改
	for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}// 作用:将原数组 a 的第 x 个元素加上 y,并维护树状数组 c
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
    	add(i,a[i]);
	}
	while(m--){
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1) add(x,y);
		else cout<<sum(y)-sum(x-1)<<endl;// 利用前缀和思想求区间和 
	}
    return 0;
}

前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

题目传送门https://www.luogu.com.cn/problem/P3374


突破口

题目描述

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

  • 将某一个数加上 x;

  • 求出某区间每一个数的和。

输入格式

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

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

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

  • 1 x k 含义:将第 x 个数加上 k;

  • 2 x y 含义:输出区间 [x,y] 内每个数的和。

输出格式

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


思路

这道题是**树状数组(Binary Indexed Tree, BIT)**的入门模板题。

💡 核心思路:为什么要用树状数组?

题目中有两种操作:

  1. 单点修改:给某个数加上 xx 。
  2. 区间查询:求一段区间的和。

暴力做法的痛点:

  • 如果用普通数组,修改 是 O(1)O(1) ,但求和需要遍历区间,是 O(N)O(N) 。
  • 如果用前缀和数组,求和 是 O(1)O(1) ,但一旦某个数变了,后面的前缀和都要重新算,修改变成了 O(N)O(N) 。
  • 题目中 NN 和 MM 都高达 5×1055×105 ,如果是 O(N)O(N) 的操作,总复杂度会达到 O(NM)O(NM) ,必然超时。

树状数组的优势:

它通过一种巧妙的"二进制分组"方式,将修改查询 的时间复杂度都降低到了 O(log⁡N)O(logN)

核心原理:

树状数组 c[i] 并不直接存储原数组 a[i] 的值,而是存储一段区间 的和。这段区间的长度由 lowbit(i) 决定。

  • lowbit(x) :取出 xx 二进制表示中最低位的 1。例如 lowbit(4) (100) = 4,lowbit(6) (110) = 2。
  • c[i] 的职责 :管理区间 [i - lowbit(i) + 1, i] 的和。

树状数组的样子:

因为是按照二进制的1来拆分,所以会根据层级形成一棵树


代码分析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
int a[MAXN], c[MAXN]; // a[] 是原始数组,c[] 是树状数组(核心数组)
int n, m; // n 是数列长度,m 是操作次数

解析

  • a[MAXN]:虽然题目有原始数组,但在树状数组的很多实现中,其实可以省略 a 数组,只维护 c 数组。这里保留 a 是为了逻辑清晰。
  • c[MAXN]:这是树状数组 本体。注意 c 的下标通常从 1 开始,因为 lowbit(0) 会导致死循环。
cpp 复制代码
int lowbit(int x) {
    // 计算 x 的二进制表示中最低位的 1 所代表的值 
    return x & (-x);
}

解析:这是树状数组的灵魂。

  • 原理 :利用补码特性。-x 在计算机中等于 ~x + 1(按位取反加 1)。
  • 效果x-x 进行按位与运算(&),除了最低位的 1 以外,其他位都会变成 0。
  • 作用:它告诉我们要"跳"多远。比如在查询时,它决定了我们要减去多少才能跳到下一个负责更小范围的节点。
cpp 复制代码
int sum(int x) {
    // sum 函数:查询前缀和
    int res = 0;
    for(int i = x; i > 0; i -= lowbit(i)) res += c[i];
    return res;
}

解析查询操作(区间求和的基础)

  • 目标 :计算原数组 a[1]a[x] 的总和。
  • 逻辑 :我们将区间 [1, x] 拆分成若干个由 c 数组管理的子区间。
  • 过程
    1. 先把 c[x] 加上(它管理着以 x 结尾的一段)。
    2. 然后 i -= lowbit(i),跳到剩下的前缀的结尾。
    3. 重复直到 i 变为 0。
  • 复杂度:因为每次至少减去二进制的一位,所以循环次数是 log⁡2Nlog2N 。
cpp 复制代码
void add(int x, int y) {
    // add 函数:单点修改
    for(int i = x; i <= n; i += lowbit(i)) c[i] += y;
}

解析修改操作

  • 目标 :给 a[x] 加上 y
  • 逻辑 :因为 a[x] 被包含在多个 c 数组的节点中,所以 a[x] 变了,所有包含它的 c[i] 都要更新。
  • 过程
    1. 更新 c[x]
    2. i += lowbit(i):跳到包含当前区间的父节点(范围更大的节点)。
    3. 一直更新直到超出数组长度 n
cpp 复制代码
int main() {
    cin >> n >> m;
    // 初始化
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        add(i, a[i]); // 将初始值插入树状数组
    }
    
    while(m--) {
        int op, x, y;
        cin >> op >> x >> y;
        if(op == 1) add(x, y); // 操作1:单点修改,a[x] += y
        else cout << sum(y) - sum(x - 1) << endl; // 操作2:区间查询
    }
    return 0;
}

解析

  • 初始化 :通过循环调用 add,将初始数组构建成树状数组。时间复杂度 O(Nlog⁡N)O(NlogN) 。
  • 操作 1 :直接调用 add(x, y),表示位置 x 的值增加了 y
  • 操作 2 :利用前缀和思想
    • 要求区间 [x, y] 的和,等价于:(1 到 y 的和) - (1 到 x-1 的和)
    • sum(y) - sum(x - 1)

📌 总结

这段代码展示了树状数组最标准、最简洁的写法:

  1. lowbit 负责导航(找爸爸或找前一个兄弟)。
  2. add 负责自底向上更新(修改自己,通知所有包含自己的父节点)。
  3. sum 负责自顶向下(或跳跃式)累加(把大区间拆成几个小区间加起来)。
  4. main 中利用 sum(y) - sum(x-1) 巧妙解决任意区间求和问题。
相关推荐
故事和你917 小时前
洛谷-算法2-4-字符串2
开发语言·数据结构·c++·算法·深度优先·动态规划·图论
郝学胜-神的一滴7 小时前
干货版《算法导论》 02 :算法效率核心解密
java·开发语言·数据结构·c++·python·算法
stolentime7 小时前
AT_agc061_d [AGC061D] Almost Multiplication Table题解
c++·算法·构造
WL_Aurora7 小时前
Python 算法基础篇之回溯
python·算法
智者知已应修善业7 小时前
【51单片机控制的交通信号灯三按键切换调节时分秒加减】2023-8-26
c++·经验分享·笔记·算法·51单片机
MicroTech20257 小时前
量子退火赋能:微算法科技(NASDAQ: MLGO)图像分割算法开启未来科技新视界
科技·算法·量子计算
枕星而眠7 小时前
C语言数组专题:从一维到二维,吃透内存与指针
java·数据结构·算法
zmzb01037 小时前
C++课后习题训练记录Day120
开发语言·c++
ximu_polaris7 小时前
设计模式(C++)-行为型模式-状态模式
c++·设计模式·状态模式