树状数组c[x]存储某一数组b的元素在区间[x-lowbit(x),x]的统计.
前缀和维护原理
对
于
正
整
数
x
,
其
二
进
制
表
示
可
写
为
∑
i
=
1
n
2
a
[
i
]
的
形
式
,
若
数
组
a
递
减
排
序
,
对于正整数x,其二进制表示可写为\sum_{i=1}^{n} 2^{a[i]} 的形式,若数组a递减排序,
对于正整数x,其二进制表示可写为i=1∑n2a[i]的形式,若数组a递减排序,
则
有
x
−
l
o
w
b
i
t
(
x
)
=
∑
i
=
1
n
−
1
2
a
[
i
]
,
此
时
划
定
区
间
[
∑
i
=
1
n
−
1
2
a
[
i
]
+
1
,
∑
i
=
1
n
2
a
[
i
]
]
,
则有x-lowbit(x) = \sum_{i=1}^{n-1} 2^{a[i]} ,此时划定区间[\sum_{i=1}^{n-1} 2^{a[i]}+1,\sum_{i=1}^{n} 2^{a[i]} ],
则有x−lowbit(x)=i=1∑n−12a[i],此时划定区间[i=1∑n−12a[i]+1,i=1∑n2a[i]],
此
时
令
x
=
x
−
l
o
w
b
i
t
(
x
)
,
重
复
上
述
操
作
直
至
x
=
0
,
则
此
时
有
若
干
区
间
:
此时令x=x-lowbit(x),重复上述操作直至x = 0,则此时有若干区间:
此时令x=x−lowbit(x),重复上述操作直至x=0,则此时有若干区间:
[
1
,
∑
i
=
1
1
2
a
[
i
]
]
,
[
∑
i
=
1
1
2
a
[
i
]
+
1
,
∑
i
=
1
2
2
a
[
i
]
]
,
.
.
.
.
.
.
,
[
∑
i
=
1
n
−
2
2
a
[
i
]
+
1
,
∑
i
=
1
n
−
1
2
a
[
i
]
]
,
[
∑
i
=
1
n
−
1
2
a
[
i
]
+
1
,
∑
i
=
1
n
2
a
[
i
]
]
,
[1,\sum_{i=1}^{1}2^{a[i]}],[\sum_{i=1}^{1}2^{a[i]}+1,\sum_{i=1}^{2}2^{a[i]}],......,[\sum_{i=1}^{n-2}2^{a[i]}+1,\sum_{i=1}^{n-1}2^{a[i]}],[\sum_{i=1}^{n-1}2^{a[i]}+1,\sum_{i=1}^{n}2^{a[i]}],
[1,i=1∑12a[i]],[i=1∑12a[i]+1,i=1∑22a[i]],......,[i=1∑n−22a[i]+1,i=1∑n−12a[i]],[i=1∑n−12a[i]+1,i=1∑n2a[i]],
我
们
以
c
[
x
]
代
表
数
组
b
在
区
间
[
x
−
l
o
w
b
i
t
(
x
)
+
1
,
x
]
元
素
的
值
的
总
和
,
我们以c[x]代表数组b在区间[x-lowbit(x)+1,x]元素的值的总和,
我们以c[x]代表数组b在区间[x−lowbit(x)+1,x]元素的值的总和,
即
c
[
x
]
=
∑
i
=
x
−
l
o
w
b
i
t
(
x
)
+
1
x
b
[
i
]
,
则
由
上
述
有
数
组
b
的
前
缀
和
公
式
:
即c[x]=\sum_{i=x-lowbit(x)+1}^{x}b[i],则由上述有数组b的前缀和公式:
即c[x]=i=x−lowbit(x)+1∑xb[i],则由上述有数组b的前缀和公式:
s
u
m
[
x
]
=
∑
i
=
1
∑
j
=
1
1
2
a
[
j
]
b
[
i
]
+
∑
i
=
(
∑
j
=
1
1
2
a
[
j
]
+
1
)
∑
j
=
1
2
2
a
[
j
]
b
[
i
]
+
.
.
.
+
∑
i
=
(
∑
j
=
1
n
−
1
2
a
[
j
]
+
1
)
∑
j
=
1
n
2
a
[
j
]
b
[
i
]
=
c
[
2
a
[
1
]
]
+
c
[
2
a
[
1
]
+
2
a
[
2
]
]
+
.
.
.
+
c
[
x
−
l
o
w
b
i
t
(
x
)
]
+
c
[
x
]
;
sum[x] = \sum_{i=1}^{\sum_{j=1}^{1}2^{a[j]}}b[i]+\sum_{i=(\sum_{j=1}^{1}2^{a[j]}+1)}^{\sum_{j=1}^{2}2^{a[j]}}b[i]+...+\sum_{i=(\sum_{j=1}^{n-1}2^{a[j]}+1)}^{\sum_{j=1}^{n}2^{a[j]}}b[i] = c[2^{a[1]}]+c[2^{a[1]}+2^{a[2]}]+...+c[x-lowbit(x)]+c[x];
sum[x]=i=1∑∑j=112a[j]b[i]+i=(∑j=112a[j]+1)∑∑j=122a[j]b[i]+...+i=(∑j=1n−12a[j]+1)∑∑j=1n2a[j]b[i]=c[2a[1]]+c[2a[1]+2a[2]]+...+c[x−lowbit(x)]+c[x];
由
上
述
我
们
得
到
了
利
用
树
状
数
组
c
求
数
组
b
前
缀
和
的
一
种
方
法
,
具
体
代
码
实
现
如
下
:
由上述我们得到了利用树状数组c求数组b前缀和的一种方法,具体代码实现如下:
由上述我们得到了利用树状数组c求数组b前缀和的一种方法,具体代码实现如下:
ll ans = 0;
for(;x;x-=lowbit(x))
ans += c[x];
性质
对于数组c的树状结构,该结构满足以下性质:
- 每个节点c[x]保存以它为根的子树中所有叶节点的和。
- 每个节点c[x]的子节点个数等于lowbit(x)的位数。
- 除了树根外,每个节点c[x]的父节点为c[x+lowbit[x]]。
- 树的深度为O(logN)。
简单理解:
对于x,c[x]对应区间为[x-lowbit(x)+1,x],我们寻找一个最小的数y(最近),使得c[y]对应区间包含且大于区间[x-lowbit(x)+1,x],则有:
{
y
−
l
o
w
b
i
t
(
y
)
<
=
x
−
l
o
w
b
i
t
(
x
)
y
>
x
(
此
处
因
为
包
含
关
系
故
y
≠
x
)
⇒
x
<
y
<
=
x
+
l
o
w
b
i
t
(
y
)
−
l
o
w
b
i
t
(
x
)
\begin{cases} & y-lowbit(y)<=x-lowbit(x) \\ & y >x(此处因为包含关系故y\ne x)\\ \end{cases} \Rightarrow x<y<=x+lowbit(y)-lowbit(x)
{y−lowbit(y)<=x−lowbit(x)y>x(此处因为包含关系故y=x)⇒x<y<=x+lowbit(y)−lowbit(x)
由于lowbit(y)和lowbit(x)二进制下含有1的位都只有一个一位,则可得lowbit(y)比lowbit(x)大一位,即lowbit(y) = 2*lowbit(x),由此即可推得性质3,若此进一步推进还可得到另外几条性质.
关于lowbit运算
lowbit(x)表示x二进制表示下最低位的1表示的值,当我们将x的二进制所有位取反时,原先lowbit(x)位置变成0,其后面的数变成1,此时我们将x加1,则最开始lowbit(x)的位置变成1,其后面的位置都为0,前面的位置各位的值不同,此时进行且运算,得到的值即为lowbit(x)的值,由于我们计算机的工作原理,以上过程可写为lowbit(x)=x&-x.
单点操作
给序列中的元素a[x]加上一个数d,同时维护数组a的前缀和,此时由此节点c[x]开始对其祖先进行更新,由于树的最大深度为logN,故时间复杂度为O(logN),具体代码实现如下:
#define lowbit(x) (x&-x)
void add(int x,int d)
{
for(;x<=N,x+=lowbit(x))
c[x] += d;
}
初始化
- 建立一个全为0的数组c,对每个x执行add(x,a[x])操作. 时间复杂度O(NlogN).
- 从小到大扫描每个节点x,借助lowbit运算扫描子节点并求和. 时间复杂度O(N).
例题与应用
题目来源:POJ - 2352
题目大意:给你星星的坐标(y递增,若y相等,x递增),每个星星都有一个等级,规定它的等级就是在它左下方的星星的个数。输入所有星星后,依次输出等级为0到n-1的星星的个数。
解题思路:由于y递增,我们可以由1~n正序扫描输入的数组,然后用树状数组c[i]代表x坐标在[x-lowbit+1,x]之间的星星的出现次数,依次更新c[x],当第i次更新时,第i颗星星的level即为前缀和sum[x[i]],由于树状数组下标不能小于等于0,我们对x坐标加一,具体代码实现如下.
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 32005;
int a[maxn],x,y,n,ans[maxn];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;++i)
{
scanf("%d%d",&x,&y);
int res = 0;
for(int j = x+1;j;j-=(j&(-j)))
res += a[j];
ans[res]++;
for(int j = x+1;j <= maxn;j+=(j&(-j)))
a[j]++;
}
for(int i = 0;i < n;++i)
printf("%d\n",ans[i]);
return 0;
}