洛谷 P2068 统计和 题解
洛谷 P2068 统计和 题解
完整题目:
P2068 统计和
题目描述
给定一个长度为 \(n(0\leq n\leq 10^5)\),初始值都为 \(0\) 的序列,\(x(0\leq x\leq 10^5)\) 次的修改某些位置上的数字,每次加上一个数,并在此期间提出 \(y(0\leq y\leq 10^5)\) 个问题,求每段区间的和。
输入格式
第一行 \(1\) 个整数,表示序列的长度 \(n\)。
第二行 \(1\) 个整数,表示操作的次数 \(w(0\leq w\leq 2\times 10^5)\)。
后面依次是 \(w\) 行,分别表示加入和询问操作。
其中,加入用x表示,询问用y表示。
\(x\) 的格式为x a b表示在序列上第 \(a\) 个数加上 \(b\)。保证 \(1 \leq a \leq n\),\(1 \leq b \leq 10^9\)。
\(y\) 的格式为y a b表示询问 \(a\) 到 \(b\) 区间的加和。保证 \(1 \leq a \leq b \leq 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]\)
树状数组:\(C[1] ,C[2],C[3],C[4],C[5],C[6],C[7],C[8]\)
好,\(A\) 数组是输入的,那么树状数组里的值又是什么?这个时候我们就要用到二进制的知识了,也是树状数组的一大难点:\(lowbit()\)
有人就问了:不就一个 return x&(-x); 吗,有什么难的?
好,我先不回答,我想问一句你知道问什么要用这个公式吗?
我来讲一下:
一个十进制的数在计算机中存储的是二进制,大家都知道,例如 6 的二进制是 110,可是,一个负数的二进制是什么呢?
在说这个之前,大家需要知道几个名词:原码,反码,补码。
一个正数的源码、反码、补码都是一样的,负数就不一样了,负数的反码就是第一位(符号位)不变,其他位取反,补码就是反码+1。什么?你问原码是什么?你先想想 “原” 这个字什么意思。
理解了原码、反码、补码,我们再来说说 \(lowbit()\) 。
\(lowbit (x)\) 指的是 \(x\) 的二进制表示中,最低位的 1 所对应的值。
好的,言归正传,树状数组为什么需要 \(lowbit\)。
树状数组的本质是 用二进制的方式拆分区间,通过“分块存储”实现高效更新和查询。而 \(lowbit\) 正是用来定义“每个节点负责的区间范围”。
具体来说,树状数组的每个节点 \(tree[i]\) 并不像前缀和数组那样存储从 \(1\) 到 \(i\) 的总和,而是存储 以 \(i\) 为终点、长度为 \(lowbit (i)\)的区间的和。
树状数组的两大核心操作(更新、查询)都依赖 \(lowbit\) 实现“跳步”,从而将复杂度压缩到 \(O (log n)\)。
我们再来看这个:

原数组:\(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]\)
知道了\(lowbit()\) 的功能,我们来看看树状数组里存的是什么。
\(C[1]\):\(A[1]\)
\(C[2]\):\(C[1]+A[2]\)
\(C[3]\):\(A[3]\)
\(C[4]\):\(C[1]+C[2]+C[3]+A[4]\)
\(C[5]\):\(A[5]\)
\(C[6]\):\(C[5]+A[6]\)
\(C[7]\):\(A[7]\)
\(C[8]\):\(C[1]+C[2]+C[3]+A[4]+C[5]+C[6]+C[7]+A[8]\)
ok,那我们换一种方式写出来:
\(C[1]\):\(A[1]\)
\(C[2]\):\(A[1]+A[2]\)
\(C[3]\):\(A[3]\)
\(C[4]\):\(A[1]+A[2]+A[3]+A[4]\)
\(C[5]\):\(A[5]\)
\(C[6]\):\(a[5]+A[6]\)
\(C[7]\):\(A[7]\)
\(C[8]\):\(A[1]+A[2]+A[3]+A[4]+a[5]+A[6]+A[7]+A[8]\)
这就是前面说的二进制拆分,\(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 见祖宗。
有任何问题请指出,如果对你有帮助,点个赞再走吧!谢谢!
浙公网安备 33010602011771号