树状数组是一种数据结构,常用于区间修改、区间查询等问题,功能没有线段树多,但胜在常数比线段树小了许多,且代码较短,更容易实现。
树状数组本质上就是一个数组,他们之间的关系就像一棵树一样,层层管理。
不妨设原数组为
那么
其实就是一个二进制数最后的
并且因为补码的原因,还有
对于每一个
举个例子:
不难瞪出直接管理
建树思路
对于每一个
父亲编号证明
我们刚刚只是用眼睛发现了
为了方便,我们先设
我们先来证明
这个很显然,因为
那么就一定会有
又有
所以
我们再来证明
假设
假设还有一个数字
因为我们知道
由于
也就是说他们三个数应该长这个样子:
注意到
所以
树状数组建树
根据刚刚的结论,我们也可以算出,管理
所以,我们就可以暴力找出所有管理
代码:
1 | for(int i=1;i<=n;i++) |
建树思路
对于每一个
所以直接找到直接管理
代码:
1 | for(int i=1;i<=n;i++) |
优化:如果你想要节省内存,那么数组
代码: 1
2
3
4
5
6for(int i=1;i<=n;i++)
{
int num=i+lowbit(i);
if(num<=n)
t[num]+=t[i];
}
树状数组的修改
根据建树所提到的,只需修改管理该节点的所有节点即可。
1 | void add(int x,int y)//将第x个数的值增加y |
树状数组的查询
树状数组这种数据结构可以做到
1 | void get(int x)//查询前x个数的值 |
树状数组维护信息
单点修改与区间查询
操作
操作
解得答案。
代码: 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
47
48
49
50
51
52
53
54
using namespace std;
int lowbit(int i)
{
return i&-i;
}
int t[500001];
int n,m;
void build()
{
for(int i=1;i<=n;i++)
{
int num=i+lowbit(i);
if(num<=n)
t[num]+=t[i];
}
}
void change(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
t[i]+=y;
}
int get(int x)
{
int num=0;
for(int i=x;i;i-=lowbit(i))
num+=t[i];
return num;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",t+i);
build();
for(int i=1;i<=m;i++)
{
int op;
scanf("%d",&op);
if(op==1)
{
int x,k;
scanf("%d%d",&x,&k);
change(x,k);
}
else
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",get(y)-get(x-1));
}
}
return 0;
}
区间修改与单点查询
众所周知,原序列的区间修改操作在差分序列上会变成两次单点修改,而这时的单点查询操作就会变成一个区间查询操作。
利用这个性质,我们就可以使用树状数组来维护原序列的差分序列来做这道题。
大体步骤是这样的:
首先计算原序列的差分序列并建树。
对于修改操作,将差分序列第
对与查询操作,即为查询差分序列前
代码: 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
47
48
49
50
51
52
53
54
55
56
57
using namespace std;
int lowbit(int i)
{
return i&-i;
}
int t[500001];
int n,m;
void build()
{
for(int i=1;i<=n;i++)
{
int num=i+lowbit(i);
if(num<=n)
t[num]+=t[i];
}
}
void change(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
t[i]+=y;
}
int get(int x)
{
int num=0;
for(int i=x;i;i-=lowbit(i))
num+=t[i];
return num;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",t+i);
for(int i=n;i;i--)
t[i]-=t[i-1];
build();
for(int i=1;i<=m;i++)
{
int op;
scanf("%d",&op);
if(op==1)
{
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
change(x,k);
change(y+1,-k);
}
else
{
int x;
scanf("%d",&x);
printf("%d\n",get(x));
}
}
return 0;
}
区间修改与区间查询
首先我们知道由于区间修改操作的存在,我们没有更好的方法来进行修改,所以还是考虑维护差分序列,下面我们来推式子:
设
所以只需要使用两个树状数组维护信息即可。
代码: 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using namespace std;
long long a[100010];
long long b[100010];
long long c[100010];
long long n,m;
inline long long read()
{
long long s=0,w=1;
char ch;
while((ch=getchar())>'9'||ch<'0')
if(ch=='-')
w=-1;
while(ch>='0'&&ch<='9')
{
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
inline long long lowbit(long long i)
{
return i&(-i);
}
inline void build()
{
for(long long i=1;i<=n;i++)
{
long long num=i;
while(num<=n)
{
b[num]+=a[i];
c[num]+=a[i]*i;
num+=lowbit(num);
}
}
}
inline void change(long long x,long long k)
{
long long num=x;
while(num<=n)
{
b[num]+=k;
c[num]+=x*k;
num+=lowbit(num);
}
}
inline long long get(long long x)
{
long long ans=0,num=x;
while(num)
{
ans+=b[num];
num-=lowbit(num);
}
ans*=(x+1);
num=x;
while(num)
{
ans-=c[num];
num-=lowbit(num);
}
return ans;
}
int main()
{
n=read(),m=read();
for(long long i=1;i<=n;i++)
a[i]=read();
for(long long i=n;i;i--)
a[i]-=a[i-1];
build();
for(long long i=1;i<=m;i++)
{
long long op;
op=read();
if(op==1)
{
long long l,r,k;
l=read(),r=read(),k=read();
change(l,k);
change(r+1,-k);
}
else
{
long long l,r;
l=read(),r=read();
printf("%lld\n",get(r)-get(l-1));
}
}
return 0;
}