蓝桥杯--树状数组1 AcWing 1264. 动态求连续区间和

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);
			}
		}	
	}
}


树状数组还是很精妙的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值