AcWing 1264. 动态求连续区间和
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。
输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n 个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。
数列从 1 开始计数。
输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。
数据范围
1≤n≤100000,
1≤m≤100000,
1≤a≤b≤n
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35
题意:给出一个序列,实现区间求和及单点修改两个功能
如果使用遍历的常规做法的话,求和复杂度为O(n),修改复杂度为O(1),而如果使用前缀和做法的话,求和复杂度简化为O(1),但修改的复杂度则会增加至O(n),由此这里引入树状数组的做法,使区间求和与单点修改两种操作的复杂度都稳定为O(logn)
简单来说,树状数组与前缀和数组相似,但前缀和覆盖的区间是从头至当前位置的全部和值,而线段数组中每个元素覆盖的则是一个由下标决定的范围值。
图片来源https://www.acwing.com/solution/content/7526/
这里要引入一种操作lowbit
public static int lowbit(int x) {
return x&-x;
}
在二进制角度来看,它的作用是计算出一个二进制数最低位1的值,或者说从低至高位有k个连续的0,它的值即是2k,而在本题中的意义即是计算出树状数组的覆盖范围
首先读入序列w[ ],建立树状数组tr[ ]
如令x=12,转换为八位二进制即是00001100,其中最低位1代表的值为4,换句话说最低位有2个连续的0,代表值为22 =4,那么树状数组tr[8]能够覆盖的范围即是4个元素w[5]~w[8]
则tr[x]=( w[ x-lowbit(x) ],w[x] ]
这里关于为什么lowbit即是覆盖范围及二进制之间的运算不再赘述,可以参考https://zhuanlan.zhihu.com/p/25185969
当明确数组的定义后,即可分别进行两种操作
可以编写函数来计算给定目标x的前缀和,由于每个tr[x]记录的范围是所有前缀的一部分,可以通过递归来一步步累加求和至tr[1],每次缩短都范围都是更新后的lowbit(x)
public static int query(int x) {
int res=0;
for(int i=x;i>0;i-=lowbit(i)) {
res+=tr[i];
}
return res;
}
另一方面,每当更新一个点的值后,也要将覆盖当前点的其他点更新,即逆向使用lowbit(x),每次递归更新至tr[n]
public static void add(int x,int v) {
for(int i=x;i<=n;i+=lowbit(i)) {
tr[i]+=v;
}
}
通过这两种操作即可实现单点修改和区间求和 代码如下
import java.io.*;
import java.util.*;
public class Main {
static Scanner tab = new Scanner(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
static int N = 100010;
static int n;
static int m;
static int tr[]=new int [N];
public static int lowbit(int x) {
return x&-x;
}
public static void add(int x,int v) {
for(int i=x;i<=n;i+=lowbit(i)) {
tr[i]+=v;
}
}
public static int query(int x) {
int res=0;
for(int i=x;i>0;i-=lowbit(i)) {
res+=tr[i];
}
return res;
}
public static void main(String[] args) throws IOException {
n=tab.nextInt();
m=tab.nextInt();
int w[]=new int [N];
for(int i=1;i<=n;i++) {
w[i]=tab.nextInt();
}
for(int i=1;i<=n;i++) {
add(i,w[i]);
}
while(m-->0) {
int k=tab.nextInt();
int a=tab.nextInt();
int b=tab.nextInt();
if(k==0) {
System.out.println(query(b)-query(a-1));
}
else {
add(a,b);
}
}
}
}
树状数组还是很精妙的