题解:P3374 【模板】树状数组 1

· · 题解

题意:

有一个由 n 个数字组成的数列,对这个数列进行 m 次操作:

树状数组

引入

一个包含n个数的序列 2,7,1,12,5,9,\dots, 计算前 i 个数的和值,称为前缀和。

累加求前 $n$ 个数的和值需要 $O(n)$ 时间。而且若对 $a[i]$ 进行修改,则 $sum[i],sum[i+1],\dots,sum[n]$ 都需要修改,最坏的情况下需要 $O(n)$ 时间。 树状数组可以高效实现,其查询前缀和与点更新均为 $O(\log n)$。 那么树状数组是如何巧妙地实现呢? ### 正文 树状数组引入了分级管理制度,设置一个管理小组,每个管理员管理一个或多个连续的元素。例如,数列有 $9$ 个元素,分别用 $a[1],a[2],\dots,a[9]$ 存储,管理数组为 $c[]$。管理数组 $c[]$ 是树状的,因此称为树状数组。 ![](https://cdn.luogu.com.cn/upload/image_hosting/btxm391y.png) 树状数组,又称为二进制索引树 $(Binary Indexed Trees)$,通过二进制分解划分区间。那么 $c[i]$ 存储的是哪些值? ### 1.区间长度 若 $i$ 的二进制表示末尾有 $k$ 个连续的 $0$,则 $c[i]$ 存储的区间长度为 $2^k$,**从** $a[i]$ **向前数** $2^k$ **个元素**,即 $c[i]=s[i-2^k+1]+s[i-2^k+2]+,\dots,+a[i]$。 ![](https://cdn.luogu.com.cn/upload/image_hosting/8muf48us.png) 举例: - $c[6]=a[5]+a[6]

3.查询前缀和

i 个元素的前缀和 sum[i] 等于 c[i] 加上 c[i] 的前驱。

![](https://cdn.luogu.com.cn/upload/image_hosting/x7i89w83.png) ### 4.点更新  若对 $a[i]$ 进行修改。 - 令 $a[i]$ 加上一个数 $z$,则只需更新 $c[i]$ 及其后继(祖先)。 - 即令这些节点都加上 $z$ 即可,无需修改其他节点。 例如,修改 $a[5]$,令其加 $2$。只需 $c[5]+2$,然后 $c[5]$ 的后继分别加 $2$,即 $c[6]+2,c[8]+2,c[16]+2,c[32]+2,\dots$。 ### 5.查询区间和 若求区间和值 $a[i]+a[i+1]+,\dots,+a[j]$,则求解前 $j$ 个元素的和值减去前 $i-1$ 个元素的和值即可。即 $sum[j]-sum[i-1]$。 ### 思路 套模板就行。 ```cpp #include<bits/stdc++.h> using namespace std; long long m,n,x,y,z; long long a[1000005],c[1000005]; long long lowbit(long long i)//求lowbit { return (-i)&i; } void add(long long i,long long z)//添加操作 { for(;i<=n;i+=lowbit(i)) c[i]=c[i]+z; } long long sum(long long i)//求和操作 { long long s=0; for(;i>0;i-=lowbit(i)) s+=c[i]; return s; } int main(){ scanf("%lld%lld",&n,&m);//输入 for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); add(i,a[i]); } for(int i=1;i<=m;i++) { scanf("%lld%lld%lld",&z,&x,&y); if(z==1)add(x,y); else printf("%lld\n",sum(y)-sum(x-1));//求区间和,输出 } return 0; } ```