洛谷 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\) 就是用来求树状数组这个元素是哪个原数组区间的和。

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

  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 见祖宗。


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

posted @ 2025-08-05 21:17  sunhy2012  阅读(16)  评论(0)    收藏  举报