树状数组学习记录

线段树和树状数组,是两个十分相似的数据结构。他们能使对一个区间的数修改以及查询的速度提升许多。两个结构本质相同,各有优缺点,今天我们来从单点修改,单点查询,区间修改,区间查询。

个人认为树状数组比较简单,就先讲一下树状数组吧(luogu模板题)
https://www.luogu.org/problemnew/show/P3374 https://www.luogu.org/problemnew/show/P3368

树状数组

差不多长这样的

在这里插入图片描述

也长这个样子

c1=a1;
c2=a1+a2;
c3=a3;
c4=a1+a2+a3+a4;

然后悄咪咪的把它变成二进制

c0001=a0001
c0010=a0001+a0010
c0011=a0011
c0100=a0001+a0010+a0011+a0100

顿时发现了规律有没有
先来简单的介绍一下lowbit函数,他是用来链接树状数组关系的函数,说的通俗点,它每个节点存储的是他的子子孙孙的和

int lowbit {returnx&(-x)}

那么这个lowbit是什么意思呢
这个算出来的是他的最后一位非零数的位所对应的值

比如
1,3,5,7这些奇数,他们的lowbit值就是1,同样,对应到图上,我们会发现它的位置都在第一排,也就是说,他们是没有儿子的,它存储的和就是他自己,
2,6,10这些2的倍数,lowbit是2
4 8 12这些lowbit是4

这些x加上他的lowbit值就变成了他的爸爸,而减去就变成了他的弟弟(结合图观察)

lowbit (1)+1=2
lowbit (2)+2=4
lowbit (3)+3=4
lowbit (4)+4=8
lowbit (5)+5=6
.......

修改
这是我们对树状数组进行修改时的重要依据,每次先自我修改,然后向上找爸爸,对爸爸进行修改一直向上
那么就顺便附一波代码

void add(int x,int k) { //给编号为x的点加上k
	while(x<=n){ //n为数组的一个上线,通常因题而异
		tree[x]+=k;
		x=x+lowbit(x); //找爸爸
	}
}

建树时,树状数组比较简单,不像线段树那么麻烦

for(int i=1;i<=n;i++) {scanf("%d",&a);add(i,a)} //在i的位置附上a就相当于加a

顺便就来说一下修改

单点修改可以直接使用

scanf("%d%d",&a,&k);add(a,k);

区间修改时需要用一点差分的思想
如果将x到y区间加上一个k,那就是从x到n都加上一个k,再从y+1到n加上一个-k
那么是这么用的

scanf("%d%d%d",&x,&y,&k);
add(x,k),add(y+1,-k);

整个的修改就是这样,大概就是一直找爸爸的过程

求值
修改实在一直找爸爸,而求值是一直找弟弟,如果没有弟弟,它爸爸的弟弟也是他的弟弟(这个关系好复杂
首先先来看一下每个数减去他的lowbit值

1-lowbit (1)=0
2-lowbit (2)=0
3-lowbit (3)=2
4-lowbit (4)=0
5-lowbit (5)=4
6-lowbit (6)=4
7-lowbit (7)=6
8 ......

为了让这个关系看的清楚点,再把图片放一遍(hiahiahia)
在这里插入图片描述
经过对比,思考一下这个找弟弟的过程
那么他是怎么查询呢
就拿7来模拟一下

tree[7]=a[7];     7-lowbit (7)=6;
tree[6]=a[5]+a[6];     6-lowbit (6)=4;
tree[4]=a[1]+a[2]+a[3]+a[4]    4-lowbit (4)=0; 

根据这个,我们可以推出他的求值的代码

int query(int x) { //x及以前所有的和,复杂度logn
    int sum=0;
    while(x!=0){
       sum+=tree[x];
       x-=lowbit(x);
    }
    return sum;
}

所以单点求值就是简单的直接printf(“%d”,query(x));就行了
区间求值后面的减去前面的,还是比较好理解的

printf("%d",query(r)-query(l-1));

所以树状数组就是这个样子,他代码非常好打,就是在理解上有一定的难度,精髓是他的lowbit函数

附luoguAC代码

p3374

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define FOR(i,n,m) for(int i=n;i<=m;i++)
#define FR(j,n,m) for(int i=n;i>=m;i--)
#define MAX 100
#define N 500020
#define mo 1000
#define ll long long
#define ull unsigned long long

using namespace std;
int n,m,tree[N];
int lowbit(int x) {return x&(-x);}

void add(int x,int y) {
	while(x<=n){
		tree[x]+=y;
		x=x+lowbit(x);
	}
}

int query(int x){
    int ans=0;
    while(x!=0){
       ans+=tree[x];
       x-=lowbit(x);
    }
    return ans;
}

int main(){
	int a; 
	scanf("%d%d",&n,&m);
	FOR(i,1,n) scanf("%d",&a), add(i,a);
	FOR(i,1,m){
		int k,x,y;
		cin>>k>>x>>y;
		if(k==1) add(x,y);
		if(k==2) cout<<query(y)-query(x-1)<<endl;
	}
	return 0;
} 

p3368

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int n,m,tree[N];
int lowbit(int x) {return x&(-x);}

void add(int x,int y) {
	while(x<=n){
		tree[x]+=y;
		x=x+lowbit(x);
	}
}

int query(int x){
    int ans=0;
    while(x!=0){
       ans+=tree[x];
       x-=lowbit(x);
    }
    return ans;
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) {
		scanf("%d",&t);
		add(i,t),add(i+1,-t);
	}
	for(int i=1; i<=m; i++) {
		scanf("%d",&t);
		if(t==1) {
			scanf("%d%d%d",&x,&y,&z);
			add(x,z),add(y+1,-z);
		} else if(t==2) {
			scanf("%d",&x);
			printf("%d\n",query(x));
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43464026/article/details/83420305