HappyCpp

一个不会打代码的程序猿

0%

树状数组

树状数组

树状数组

对于一个数组,我们如果要求区间[l,r]之间求和,我们可以采用前缀和相减的方式,也就是a[r]-a[l-1];
这里数组中的元素都是静态的,但如果我们对数组中的元素进行修改,那么我们该怎么进行求解呢?我们暴力求解的时间复杂度为O(n),如果我们有n次查询,时间复杂度是O(n^2),这是很高的,所以我们为了降低时间复杂度,我们引入树状数组。

我们看下面这张图:

对于每一个c[x],它的长度都是lowbit(x),lowbit函数返回的是这个数二进制下最后一个1的位置。我们以c[6]和c[8]为例,6的二进制为110,那么他的长度为2^1=2(末位有多少个0),c[6]=a[5]+a[6];8的二进制为1000,那么他的长度为2^3=8,c[8]=a[1]+a[2]+…+a[8]。

我们再看一张图:

如果我们要求前14项的和我们只需要查找c[14](2^1的区间长度),c[12](2^2的区间长度),c[8](2^3的区间长度)即可,我们对照二进制发现14为1110,每次只需要将末位的1变成0也就是x-=lowbit(x)即可得到前缀和。14对应二进制变化为1110->1100->1000->0000。

我们要对某个值进行更新,显然不能只对这一点的树状数组更新,而是要对所有包含该点的树状数组进行更新,例如我们要对a[6]进行更新,那么我们要对c[6],c[8],c[16]进行更新,我们对照二进制发现,每次查找的元素下标为x+=lowbit(x),即可进行更新。6对应的二进制变化为110->1000->10000。

单点更新 区间查询

最简单的树状数组操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int c[N];
int a[N];
int n;
int lowbit(int x) {
return x&(-x);
}
int sum(int x) {
int res=0;
while(x>0) {
res+=c[x];
x-=lowbit(x);
}
return res;
}
void update(int x,int val) {
while(x<=n) {
c[x]+=val;
x+=lowbit(x);
}
}
int main() {
int q,pos,x;
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
update(i,a[i]);
}
scanf("%d",&q);
/*for(int i=1; i<=n; i++)
printf("%d ",sum[i]);
puts("");*/
while(q--) {
scanf("%d%d",&pos,&x);
update(pos,x);
for(int i=1; i<=n; i++)
printf("%d\n",sum(i));
}
return 0;
}

附一道题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166

区间更新 单点查询

首先我们来看一下差分数组,我们设定一个数组a[]={3,5,2,4,8},那么他的差分数组为b[]={3,2,-3,2,4},易证a[i]=b[1]+b[2]+....+b[i],即差分数组的前缀和为对应原数组的元素。如果我要对原数组中[2,4]区间中的每个元素+2,我们来看一下a[]={3,7,4,6,8},b []={3,4,-3,2,2}; 我们发现差分数组中只有b[2]和b[5]的值改变,且b[2]增加2,b[5]减少2,也就是说我们在对区间更新时,只需对差分数组b[l]+val,b[r+1]-val即可,再求区间和的时候需要跑一遍前缀和,但这是时间复杂度为O(1)的更新,O(n)的查询。所以我们需要对差分数组用树状数组维护一下,将时间复杂度降到O(logn)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int a[N],b[N],c[N];
int n;
int lowbit(int x) {
return x&(-x);
}
int sum(int x) {
int res=0;
while(x>0) {
res+=c[x];
x-=lowbit(x);
}
return res;
}
void update(int x,int val) {
while(x<=n) {
c[x]+=val;
x+=lowbit(x);
}
}
int main() {
int q,l,r,x,k,pos;
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
b[i]=a[i]-a[i-1];
update(i,b[i]);
}
scanf("%d",&q);
while(q--) {
scanf("%d%d%d",&l,&r,&x);
update(l,x);
update(r+1,-x);
}
scanf("%d",&k);
while(k--)
{
scanf("%d",&pos);
printf("%d\n",sum(pos));
}
return 0;
}

这里有一些习题可供练习:
POJ 2352
CodeForces #755D
HDU 1166
HDU 1556
BZOJ 1878
BZOJ 2743
BZOJ 1452