洛谷 P2068 统计和 题解
这是我的第八篇题解,我觉得该好好讲一次了。
完整题目:
P2068 统计和
题目描述
给定一个长度为 n ( 0 ≤ n ≤ 1 0 5 ) n(0\leq n\leq 10^5) n(0≤n≤105),初始值都为 0 0 0 的序列, x ( 0 ≤ x ≤ 1 0 5 ) x(0\leq x\leq 10^5) x(0≤x≤105) 次的修改某些位置上的数字,每次加上一个数,并在此期间提出 y ( 0 ≤ y ≤ 1 0 5 ) y(0\leq y\leq 10^5) y(0≤y≤105) 个问题,求每段区间的和。
输入格式
第一行 1 1 1 个整数,表示序列的长度 n n n。
第二行 1 1 1 个整数,表示操作的次数 w ( 0 ≤ w ≤ 2 × 1 0 5 ) w(0\leq w\leq 2\times 10^5) w(0≤w≤2×105)。
后面依次是 w w w 行,分别表示加入和询问操作。
其中,加入用x表示,询问用y表示。
x x x 的格式为x a b表示在序列上第 a a a 个数加上 b b b。保证 1 ≤ a ≤ n 1 \leq a \leq n 1≤a≤n, 1 ≤ b ≤ 1 0 9 1 \leq b \leq 10^9 1≤b≤109。
y y y 的格式为y a b表示询问 a a a 到 b b b 区间的加和。保证 1 ≤ a ≤ b ≤ n 1 \leq a \leq b \leq n 1≤a≤b≤n。输出格式
每行一个正整数,分别是每次询问的结果。
输入输出样例 #1
输入 #1
5 4 x 3 8 y 1 3 x 4 9 y 3 4输出 #1
8 17
题意分析:
这道题其实说的很明白了,单点加上一个值,区间求和,其实就是线段树或者树状数组的板子题。一道题有多中做法的时候,我会选择简单的做法 你也可以理解为代码量少的做法 所以,这道题我来讲讲树状数组。
算法讲解(知道树状数组的可以跳过,知道大概得可以跳过文字讲解,比较枯燥)
树状数组,顾名思义,就是橡树一样的数组,那他长什么样子,如何使用,怎么存储呢?
树状数组可以理解为高级的前缀和数组,但是不能完全这样理解,因为他和前缀和相似的一点就是由原数组求出来的。被我这么一说,是不是更糊涂了?看下边。

现在大家理解一点了吗?我来解释一下。
原数组:
A
[
1
]
,
A
[
2
]
,
A
[
3
]
,
A
[
4
]
,
A
[
5
]
,
A
[
6
]
,
A
[
7
]
,
A
[
8
]
A[1] ,A[2],A[3],A[4],A[5],A[6],A[7],A[8]
A[1],A[2],A[3],A[4],A[5],A[6],A[7],A[8]
树状数组:
C
[
1
]
,
C
[
2
]
,
C
[
3
]
,
C
[
4
]
,
C
[
5
]
,
C
[
6
]
,
C
[
7
]
,
C
[
8
]
C[1] ,C[2],C[3],C[4],C[5],C[6],C[7],C[8]
C[1],C[2],C[3],C[4],C[5],C[6],C[7],C[8]
好,
A
A
A 数组是输入的,那么树状数组里的值又是什么?这个时候我们就要用到二进制的知识了,也是树状数组的一大难点:
l
o
w
b
i
t
(
)
lowbit()
lowbit()
有人就问了:不就一个 return x&(-x); 吗,有什么难的?
好,我先不回答,我想问一句你知道问什么要用这个公式吗?
我来讲一下:
一个十进制的数在计算机中存储的是二进制,大家都知道,例如 6 的二进制是 110,可是,一个负数的二进制是什么呢?
在说这个之前,大家需要知道几个名词:原码,反码,补码。
一个正数的源码、反码、补码都是一样的,负数就不一样了,负数的反码就是第一位(符号位)不变,其他位取反,补码就是反码+1。什么?你问原码是什么?你先想想 “原” 这个字什么意思。
理解了原码、反码、补码,我们再来说说
l
o
w
b
i
t
(
)
lowbit()
lowbit() 。
l
o
w
b
i
t
(
x
)
lowbit (x)
lowbit(x) 指的是
x
x
x 的二进制表示中,最低位的 1 所对应的值。
好的,言归正传,树状数组为什么需要
l
o
w
b
i
t
lowbit
lowbit。
树状数组的本质是 用二进制的方式拆分区间,通过“分块存储”实现高效更新和查询。而
l
o
w
b
i
t
lowbit
lowbit 正是用来定义“每个节点负责的区间范围”。
具体来说,树状数组的每个节点
t
r
e
e
[
i
]
tree[i]
tree[i] 并不像前缀和数组那样存储从
1
1
1 到
i
i
i 的总和,而是存储 以
i
i
i 为终点、长度为
l
o
w
b
i
t
(
i
)
lowbit (i)
lowbit(i)的区间的和。
树状数组的两大核心操作(更新、查询)都依赖
l
o
w
b
i
t
lowbit
lowbit 实现“跳步”,从而将复杂度压缩到
O
(
l
o
g
n
)
O (log n)
O(logn)。
我们再来看这个:

原数组:
A
[
1
]
,
A
[
2
]
,
A
[
3
]
,
A
[
4
]
,
A
[
5
]
,
A
[
6
]
,
A
[
7
]
,
A
[
8
]
A[1] ,A[2],A[3],A[4],A[5],A[6],A[7],A[8]
A[1],A[2],A[3],A[4],A[5],A[6],A[7],A[8]
树状数组:
C
[
1
]
,
C
[
2
]
,
C
[
3
]
,
C
[
4
]
,
C
[
5
]
,
C
[
6
]
,
C
[
7
]
,
C
[
8
]
C[1] ,C[2],C[3],C[4],C[5],C[6],C[7],C[8]
C[1],C[2],C[3],C[4],C[5],C[6],C[7],C[8]
知道了
l
o
w
b
i
t
(
)
lowbit()
lowbit() 的功能,我们来看看树状数组里存的是什么。
C
[
1
]
C[1]
C[1]:
A
[
1
]
A[1]
A[1]
C
[
2
]
C[2]
C[2]:
C
[
1
]
+
A
[
2
]
C[1]+A[2]
C[1]+A[2]
C
[
3
]
C[3]
C[3]:
A
[
3
]
A[3]
A[3]
C
[
4
]
C[4]
C[4]:
C
[
1
]
+
C
[
2
]
+
C
[
3
]
+
A
[
4
]
C[1]+C[2]+C[3]+A[4]
C[1]+C[2]+C[3]+A[4]
C
[
5
]
C[5]
C[5]:
A
[
5
]
A[5]
A[5]
C
[
6
]
C[6]
C[6]:
C
[
5
]
+
A
[
6
]
C[5]+A[6]
C[5]+A[6]
C
[
7
]
C[7]
C[7]:
A
[
7
]
A[7]
A[7]
C
[
8
]
C[8]
C[8]:
C
[
1
]
+
C
[
2
]
+
C
[
3
]
+
A
[
4
]
+
C
[
5
]
+
C
[
6
]
+
C
[
7
]
+
A
[
8
]
C[1]+C[2]+C[3]+A[4]+C[5]+C[6]+C[7]+A[8]
C[1]+C[2]+C[3]+A[4]+C[5]+C[6]+C[7]+A[8]
ok,那我们换一种方式写出来:
C
[
1
]
C[1]
C[1]:
A
[
1
]
A[1]
A[1]
C
[
2
]
C[2]
C[2]:
A
[
1
]
+
A
[
2
]
A[1]+A[2]
A[1]+A[2]
C
[
3
]
C[3]
C[3]:
A
[
3
]
A[3]
A[3]
C
[
4
]
C[4]
C[4]:
A
[
1
]
+
A
[
2
]
+
A
[
3
]
+
A
[
4
]
A[1]+A[2]+A[3]+A[4]
A[1]+A[2]+A[3]+A[4]
C
[
5
]
C[5]
C[5]:
A
[
5
]
A[5]
A[5]
C
[
6
]
C[6]
C[6]:
a
[
5
]
+
A
[
6
]
a[5]+A[6]
a[5]+A[6]
C
[
7
]
C[7]
C[7]:
A
[
7
]
A[7]
A[7]
C
[
8
]
C[8]
C[8]:
A
[
1
]
+
A
[
2
]
+
A
[
3
]
+
A
[
4
]
+
a
[
5
]
+
A
[
6
]
+
A
[
7
]
+
A
[
8
]
A[1]+A[2]+A[3]+A[4]+a[5]+A[6]+A[7]+A[8]
A[1]+A[2]+A[3]+A[4]+a[5]+A[6]+A[7]+A[8]
这就是前面说的二进制拆分, l o w b i t lowbit lowbit 就是用来求树状数组这个元素是哪个原数组区间的和。
树状数组主要包括三个函数
- 求lowbit值
int lowbit(int x){
return x&(-x);
}
- 给第x个数加k
//c数组是树状数组
void update(int x,int k){
//当x不超过数组最大范围n时,继续更新
while(x<= n) {
//在树状数组的x位置加上k值
//c数组就是树状数组,用于高效存储和计算前缀和
c[x]+=k;
//将x加上其最低位的1,移动到下一个需要更新的位置
//lowbit(x)函数用于计算x的最低位1所代表的值
x += lowbit(x);//更新索引位置
}
}
- 查询区间和
//c数组是树状数组
int getsum(int x) {
//初始化总和为0
int sum=0;
//当x大于0时,继续累加
while(x>0){
//累加树状数组中x位置的值到总和
sum+=c[x];
//将x减去其最低位的1,移动到上一个需要累加的位置
x-=lowbit(x);
}
//返回计算得到的区间和
return sum;
}
那么,到这里,相信大家还不太明白,因为作者能力有限,只能讲到这里了。
你千万别说一点都没听懂,我会伤心的
作为一个良心作家,肯定不会浪费大家的时间,下面请看VCR:
VCR
好,那么下面开始解题,我在重复一下题意:
给一个数组单点加上一个值和区间求和。
那我们直接套三个函数即可,上面讲的很清楚了,不在赘述
AC代码:
#include<bits/stdc++.h>
using namespace std;
long long n,m;
long long c[110000];
char a;
int lowbit(int x){// lowbit
return x&(-x);
}
void xg(int x,int k){//单点修改
while(x<=n){
c[x]+=k;
x+=lowbit(x);
}
}
long long cx(int x){//查询区间和
long long sum=0;
while(x>0){
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main(){
cin>>n>>m;
int x,y;
for(int i=1;i<=m;i++){
cin>>a>>x>>y;
if(a=='x') xg(x,y);
if(a=='y') cout<<cx(y)-cx(x-1)<<endl;
}
return 0;
}
如果你问为什么抄了我的代码依旧零分
我只能说
十年 OI 一场空,不开 long \hspace{1mm} long 见祖宗。
有任何问题请指出,如果对你有帮助,点个赞再走吧!谢谢!
洛谷 P2068 树状数组题解
82

被折叠的 条评论
为什么被折叠?



