「题解」「数据结构」树状数组1

前言

 这里我们不描述其用法,在后面的题中详细描述

  • 单点修改,区间查询
  • 区间修改,单点查询
  • 区间修改,区间查询
  • 求逆序对数

单点修改,区间查询

题目描述

这是一道模板题。

给定数列 a[1], a[2], \dots, a[n] ,你需要依次进行 q 个操作,操作有两类:

  • 1 i x:给定 i,x ,将 a[i] 加上 x
  • 2 l r:给定 l,r ,求 \sum_{i=l}^ra[i] 的值(换言之,求 a[l]+a[l+1]+\dots+a[r] 的值)。

输入格式

第一行包含 2 个正整数 n,q ,表示数列长度和询问个数。保证 1\le n,q\le 10^6
第二行 n 个整数 a[1], a[2], \dots, a[n] ,表示初始数列。保证 |a[i]|\le 10^6
接下来 q 行,每行一个操作,为以下两种之一:

  • 1 i x:给定 i,x ,将 a[i] 加上 x
  • 2 l r:给定 l,r ,求 \sum_{i=l}^ra[i] 的值。

保证 1\le l\le r\le n, |x|\le 10^6

输出格式

对于每个 2 l r 操作输出一行,每行有一个整数,表示所求的结果。

样例

样例输入

3 2
1 2 3
1 2 0
2 1 3

样例输出

6

引入

「题解」「数据结构」树状数组1_第1张图片
这就是树状数组的雏形,我们可以清晰地看到。c[1]=a[1],c[2]=a[1]+a[2],c[3]=a[3],c[4]=a[1]+a[2]+a[3]+a[4],…
那么这种数据结构的储存究竟有什么规律呢 ?
我们将这些数字换做二进制补码看看

  1. c[0001]有一[1]个数
  2. c[0010]有两[10]个数
  3. c[0011]有一[1]个数
  4. c[0100]有四[100]个数
  5. c[0101]有五[1]个数
  6. c[0110]有两[10]个数
  7. c[0111]有一[1]个数
  8. c[1000]有八[1000]个数

细心的同学们可以发现,每个ci的数的个数为2k
进一步,我们又可以发现每一个ci的二进制表示数从后往前依次数,直到出现的第一个1,取其以及其后的所有0,就是它应有的数的个数


int lowbit(int x) {
	return x & (- x);
}

这种函数比较简单,但有些同学可能无法理解,举个例子
x=0010
-x=1010(原码)=1111(补码=~原码(符号位除外)+1)
x&(-x)=0010&1111=0010
如果连位运算都不知道的朋友,可以看看 这篇文章 QAQ

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

这里就不过多解释啦(:p)


我们回到这道题来,他要求能够单点修改,我们可以看到,如果修改了a1,那么受影响的只有c1、c2、c4和c8(比前缀和快得多)
然后我们又可以看到,c1c2差了lowbit(1),c2c4差了lowbit(2),c4c8差了lowbit(4),How wonderful!

void update(long long k, long long temp) {
	for(int i = k; i <= n; i += lowbit(i)) {
		bit[i] += temp;
	}
}

其中k为起始位置,temp为修改的值,其余的就不多说了,大家可自己比划比划


求得了树状数组的值,我们该如何求前缀和呢
又来举个栗子(香啊)
我们想要知道 ∑ i = 1 5 a [ i ] \sum_{i=1}^{5}a[i] i=15a[i]
∑ i = 1 5 a [ i ] = c [ 5 ] + c [ 4 ] \sum_{i=1}^{5}a[i]=c[5]+c[4] i=15a[i]=c[5]+c[4]
诶,5-4=lowbit(5),这是巧合吗,我们再举个例子
∑ i = 1 7 a [ i ] = c [ 7 ] + c [ 6 ] + c [ 4 ] \sum_{i=1}^{7}a[i]=c[7]+c[6]+c[4] i=17a[i]=c[7]+c[6]+c[4]
7-6=lowbit(7),6-4=lowbit(6)
自然而然,我们就可以发现这个深藏不漏的求前缀和公式

long long Sum(long long k) {
	long long sum = 0;
	for(int i = k; i > 0; i -= lowbit(i)) {
		sum += bit[i];
	}
	return sum;
}

代码

#include 

const int M = 1e6 + 5;
long long a[M], bit[M];
int n, m;

long long lowbit(long long  k) {
	return k & (- k);
}

void update(long long k, long long temp) {
	for(int i = k; i <= n; i += lowbit(i)) {
		bit[i] += temp;
	}
}

long long Sum(long long k) {
	long long sum = 0;
	for(int i = k; i > 0; i -= lowbit(i)) {
		sum += bit[i];
	}
	return sum;
}

int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i ++) {
		scanf("%lld", &a[i]);
		update(i, a[i]);				//我们的a数组为0,所以每输入一个值,就相当于给为0的a[i]加上这个值
	}
	for(int i = 1; i <= m; i ++) {
		int order;
		scanf("%d", &order);
		if(order == 1) {
			long long x, k;
			scanf("%lld %lld", &k, &x);
			update(k, x);
		}
		if(order == 2) {
			long long l, r;
			scanf("%lld %lld", &l, &r);
			printf("%lld\n", Sum(r) - Sum(l - 1));
		}
	}
	return 0;
}

区间修改,单点查询

题目描述

这是一道模板题。

给定数列 a[1], a[2], \dots, a[n] ,你需要依次进行 q 个操作,操作有两类:

  • 1 l r x:给定 l,r,x ,对于所有 i\in[l,r] ,将 a[i] 加上 x (换言之,将 a[l], a[l+1], \dots, a[r] 分别加上 x );
  • 2 i:给定 i ,求 a[i] 的值。

输入格式

第一行包含 2 个正整数 n,q ,表示数列长度和询问个数。保证 1\le n,q\le 10^6
第二行 n 个整数 a[1], a[2], \dots, a[n] ,表示初始数列。保证 |a[i]|\le 10^6
接下来 q 行,每行一个操作,为以下两种之一:

  • 1 l r x:对于所有 i\in[l,r] ,将 a[i] 加上 x
  • 2 i:给定 i ,求 a[i] 的值。

保证 1\le l\le r\le n, |x|\le 10^6

输出格式

对于每个 2 i 操作,输出一行,每行有一个整数,表示所求的结果。

样例

样例输入

3 2
1 2 3
1 1 3 0
2 2

样例输出

2

数据范围与提示

对于所有数据, 1\le n,q\le 10^6, |a[i]|\le 10^6 , 1\le l\le r\le n, |x|\le 10^6

欲知后事如何,且听下回分解

你可能感兴趣的:(数据结构,数据结构,c++)