前言
做个总结,忘记之后再翻翻
首先明确一下树状数组的结构性质:
1.每个内部节点 c [ x ] c[x] c[x]保存以它为根的子树中所有叶节点的和
2.每个内部节点 c [ x ] c[x] c[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的位数,即位为1的数量
例如:
x
=
9
=
2
3
+
2
1
x=9=2^3+2^1
x=9=23+21,分为
[
1
,
8
]
,
[
9
,
9
]
[1,8],[9,9]
[1,8],[9,9]两个区间,区间和分别为
c
[
8
]
,
c
[
9
]
c[8],c[9]
c[8],c[9]
x
=
13
=
2
3
+
2
2
+
2
0
x=13=2^3+2^2+2^0
x=13=23+22+20,分为
[
1
,
8
]
,
[
9
,
12
]
,
[
13
,
13
]
[1,8],[9,12],[13,13]
[1,8],[9,12],[13,13]三个区间,区间和分别为
c
[
8
]
,
c
[
12
]
,
c
[
13
]
c[8],c[12],c[13]
c[8],c[12],c[13]
3.除树根外,每个内部节点 c [ x ] c[x] c[x]的父节点是 c [ x + l o w b i t ( x ) ] c[x+lowbit(x)] c[x+lowbit(x)]
4.树的深度为 O ( l o g N ) O(logN) O(logN)。因此可以保证在 O ( l o g N ) O(logN) O(logN)的时间内查询前缀和
树状数组应用总结
主要分为三种:
- 单点修改,单点查询(最基础)
- 区间修改,单点查询
- 区间修改,区间查询
应用一:单点修改,单点查询
最基础的定义
查询
int ask(int x)
{
int ans=0;
for (;x;x-=x&-x)ans+=c[x];
return ans;
}
修改
void add(int x,int y)
{
for (;x<=n;x+=x&-x)c[x]+=y;
}
例题:POJ2182
题意:有
n
n
n头奶牛,身高
1
−
n
1-n
1−n不等,站成一列,已知第
i
i
i头奶牛前面有
A
i
A_i
Ai头比他矮,求出所有身高。
思路:从后往前思考。
A
n
A_n
An表示前
n
−
1
n-1
n−1头牛有
A
n
A_n
An头比最后一头矮,由于不存在重复身高,最后一头牛的身高肯定是排第
A
n
+
1
A_n+1
An+1位的,同理第
i
i
i头的身高一定是排第
A
i
+
1
A_i+1
Ai+1位,但是已经出现过的身高就得排除。可以通过维护一个初始全1的01序列,查询第
k
k
k个1的位置并且将其修改为0。用二分+树状数组即可
代码:
int n,A[maxn],c[maxn],ans[maxn];
int ask(int x){int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for(;x<=n;x+=x&-x)c[x]+=y;}
int solve(int x)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if (ask(mid)<x)l=mid+1;
else r=mid;
}
return l;
}
int main()
{
scanf("%d",&n);
A[1]=0;rep(i,2,n)scanf("%d",&A[i]);
rep(i,1,n)add(i,1);
for (int i=n;i>=1;i--)
{
ans[i]=solve(A[i]+1);
add(ans[i],-1);
}
rep(i,1,n)W(ans[i]);
}
应用二:区间修改,单点查询
新建数组
b
b
b,前缀和
b
[
1
b[1
b[1~
x
]
x]
x]反映了指令"C l r d"对
a
[
x
]
a[x]
a[x]的影响
1.把
b
[
l
]
+
d
b[l]+d
b[l]+d
2.把
b
[
r
+
1
]
−
d
b[r+1]-d
b[r+1]−d
观察一下数组
b
b
b的变化:
1.当
1
≤
x
<
l
1\leq x< l
1≤x<l时:前缀和
b
[
1
b[1
b[1~
x
]
x]
x]无变化
2.当
l
≤
x
≤
r
l \leq x \leq r
l≤x≤r时:前缀和
b
[
1
b[1
b[1~
x
]
x]
x]增加了
d
d
d,因为
b
[
l
]
b[l]
b[l]增加了
d
d
d
3.当
r
<
x
≤
n
r< x \leq n
r<x≤n时:前缀和
b
[
1
b[1
b[1~
x
]
x]
x],因为一加一减刚好抵消
也就是说 对于区间
[
l
,
r
]
[l,r]
[l,r]内的数的影响,都体现在了数组
b
b
b的前缀和中
而求前缀和就是树状数组的基础操作,累加上原先的
a
[
x
]
a[x]
a[x]即可得到"Q x"的答案
蓝书例题:
代码:
int n,m,l,r,x,a[maxn],c[maxn];
char s[5];
int ask(int x){int ans=0;for (;x;x-=x&-x)ans+=c[x];return ans;}
void add(int x,int y){for (;x<=n;x+=x&-x)c[x]+=y;}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,n)scanf("%d",&a[i]);
while(m--)
{
scanf("%s",s);
if (s[0]=='Q')
{
scanf("%d",&x);
W(ask(x)+a[x]);
}
else if (s[0]=='C')
{
scanf("%d%d%d",&l,&r,&x);
add(l,x);
add(r+1,-x);
}
}
}
应用三:区间修改,区间查询
首先仍是维护一个数组
b
b
b,
a
[
x
]
a[x]
a[x]变化量就是前缀和
b
[
1
b[1
b[1~
x
]
x]
x]
那么原数组
a
a
a的前缀和
a
[
1
a[1
a[1~
x
]
x]
x]变化量就是:
∑
i
=
1
x
∑
j
=
1
i
b
[
j
]
=
(
x
+
1
)
∑
i
=
1
x
b
[
i
]
−
∑
i
=
1
x
(
i
×
b
[
i
]
)
\sum_{i=1}^x\sum_{j=1}^i b[j]=(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x (i×b[i])
∑i=1x∑j=1ib[j]=(x+1)∑i=1xb[i]−∑i=1x(i×b[i])
第一部分就是维护
b
b
b的前缀和
第二部分就是维护
i
×
b
[
i
]
i×b[i]
i×b[i]的前缀和,令
i
×
b
[
i
]
=
c
2
[
x
]
i×b[i]=c2[x]
i×b[i]=c2[x]
由于只是乘了个
i
i
i,故与之前对应的操作是:
1.把
c
2
[
l
]
+
l
d
c2[l]+ld
c2[l]+ld
2.把
c
2
[
r
+
1
]
−
(
r
+
1
)
d
c2[r+1]-(r+1)d
c2[r+1]−(r+1)d
观察一下数组
b
b
b的变化:
当
l
≤
x
≤
r
l \leq x \leq r
l≤x≤r时:前缀和
c
2
[
1
c2[1
c2[1~
x
]
x]
x]增加了
l
d
ld
ld,因为
c
2
[
l
]
c2[l]
c2[l]增加了
l
d
ld
ld
综上所述:
a
a
a的原始前缀和+
a
a
a的前缀和变化量即为最终的
a
a
a的前缀和
S
u
m
[
r
]
=
s
u
m
[
r
]
+
(
r
+
1
)
∑
i
=
1
r
b
[
i
]
−
∑
i
=
1
r
(
i
×
b
[
i
]
)
Sum[r]=sum[r]+(r+1)\sum_{i=1}^r b[i]-\sum_{i=1}^r (i×b[i])
Sum[r]=sum[r]+(r+1)∑i=1rb[i]−∑i=1r(i×b[i])
=
s
u
m
[
r
]
+
(
r
+
1
)
a
s
k
1
(
r
)
−
a
s
k
2
(
r
)
=sum[r]+(r+1)ask1(r)-ask2(r)
=sum[r]+(r+1)ask1(r)−ask2(r)
区间查询即输出 S u m [ r ] − S u m [ l − 1 ] Sum[r]-Sum[l-1] Sum[r]−Sum[l−1]
蓝书例题:
代码:
int n,m;
ll a[maxn],c1[maxn],c2[maxn],sum[maxn],l,r,x;
char s[5];
ll ask1(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c1[x];return ans;}
ll ask2(ll x){ll ans=0;for(;x;x-=x&-x)ans+=c2[x];return ans;}
void add1(ll x,ll y){for(;x<=n;x+=x&-x)c1[x]+=y;}
void add2(ll x,ll y){for(;x<=n;x+=x&-x)c2[x]+=y;}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,n)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
while(m--)
{
scanf("%s",s);
scanf("%lld%lld",&l,&r);
if (s[0]=='Q')
{
ll ans1=sum[r]-sum[l-1];
ll ans2=(r+1)*ask1(r)-l*ask1(l-1);
ll ans3=-ask2(r)+ask2(l-1);
WW(ans1+ans2+ans3);
}
else
{
scanf("%lld",&x);
add1(l,x);
add1(r+1,-x);
add2(l,l*x);
add2(r+1,-(r+1)*x);
}
}
}