洛谷 P2068 统计和 题解

洛谷 P2068 树状数组题解

洛谷 P2068 统计和 题解

这是我的第八篇题解,我觉得该好好讲一次了。

完整题目:

P2068 统计和

题目描述

给定一个长度为 n ( 0 ≤ n ≤ 1 0 5 ) n(0\leq n\leq 10^5) n(0n105),初始值都为 0 0 0 的序列, x ( 0 ≤ x ≤ 1 0 5 ) x(0\leq x\leq 10^5) x(0x105) 次的修改某些位置上的数字,每次加上一个数,并在此期间提出 y ( 0 ≤ y ≤ 1 0 5 ) y(0\leq y\leq 10^5) y(0y105) 个问题,求每段区间的和。

输入格式

第一行 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(0w2×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 1an 1 ≤ b ≤ 1 0 9 1 \leq b \leq 10^9 1b109
y y y 的格式为 y a b 表示询问 a a a b b b 区间的加和。保证 1 ≤ a ≤ b ≤ n 1 \leq a \leq b \leq n 1abn

输出格式

每行一个正整数,分别是每次询问的结果。

输入输出样例 #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 就是用来求树状数组这个元素是哪个原数组区间的和。

树状数组主要包括三个函数

  1. 求lowbit值
int lowbit(int x){
	return x&(-x);
}

  1. 给第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);//更新索引位置
    }
}

  1. 查询区间和
//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 见祖宗。


有任何问题请指出,如果对你有帮助,点个赞再走吧!谢谢!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

—海燕—

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

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

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

打赏作者

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

抵扣说明:

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

余额充值