我是靠谱客的博主 欢喜白开水,最近开发中收集的这篇文章主要介绍_树状数组_1. 什么是树状数组?2. l o w b i t lowbit lowbit3. 一,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1. 什么是树状数组?

树状数组是一个查询和修改复杂度都为 O ⁡ ( log ⁡ n ) operatorname{O}(log n) O(logn) 的数据结构。

看到这句话是不是想到了线段树?

是的!

但是,凡是可以使用树状数组解决的问题, 使用线段树一定可以解决, 但是线段树能够解决的问题树状数组未必能够解决。

哦,那还是用线段树吧……

然鹅:

  • 线段树的数组需要开 4 4 4 倍,树状数组只用 1 1 1 倍。

  • 树状数组代码量少,线段树写 1 1 1 题用树状数组可以写 2 2 2 题。

  • 真不错!

2. l o w b i t lowbit lowbit

lowbit(x) ⁡ operatorname{lowbit(x)} lowbit(x) 就是求 x x x 最低位的 1 1 1

c c c 数组记录原数组的和:

c [ 1 ] = a [ 1 ] → c [ ( 1 ) 2 ] = a [ ( 1 ) 2 ] c[1]=a[1]to c[(1)_2]=a[(1)_2] c[1]=a[1]c[(1)2]=a[(1)2]

c [ 2 ] = a [ 2 ] + a [ 1 ] → c [ ( 10 ) 2 ] = a [ ( 10 ) 2 ] + a [ ( 1 ) 2 ] c[2]=a[2]+a[1]to c[(10)_2]=a[(10)_2]+a[(1)_2] c[2]=a[2]+a[1]c[(10)2]=a[(10)2]+a[(1)2]

c [ 3 ] = a [ 3 ] → c [ ( 11 ) 2 ] = a [ ( 11 ) 2 ] c[3]=a[3]to c[(11)_2]=a[(11)_2] c[3]=a[3]c[(11)2]=a[(11)2]

c [ 4 ] = a [ 4 ] + a [ 3 ] + a [ 2 ] + a [ 1 ] → c [ ( 100 ) 2 ] = a [ ( 100 ) 2 ] + a [ ( 11 ) 2 ] + a [ ( 10 ) 2 ] + a [ ( 1 ) 2 ] c[4]=a[4]+a[3]+a[2]+a[1]to c[(100)_2]=a[(100)_2]+a[(11)_2]+a[(10)_2]+a[(1)_2] c[4]=a[4]+a[3]+a[2]+a[1]c[(100)2]=a[(100)2]+a[(11)2]+a[(10)2]+a[(1)2]

c [ 5 ] = a [ 5 ] → c [ ( 101 ) 2 ] = a [ ( 101 ) 2 ] c[5]=a[5]to c[(101)_2]=a[(101)_2] c[5]=a[5]c[(101)2]=a[(101)2]

c [ 6 ] = a [ 6 ] + a [ 5 ] → c [ ( 110 ) 2 ] = a [ ( 110 ) 2 ] + a [ ( 101 ) 2 ] c[6]=a[6]+a[5]to c[(110)_2]=a[(110)_2]+a[(101)_2] c[6]=a[6]+a[5]c[(110)2]=a[(110)2]+a[(101)2]

c [ 7 ] = a [ 7 ] → c [ ( 111 ) 2 ] = a [ ( 111 ) 2 ] c[7]=a[7]to c[(111)_2]=a[(111)_2] c[7]=a[7]c[(111)2]=a[(111)2]

更新:

a [ 1 ] a[1] a[1] 为例,要更新 a [ 1 ] , a [ 2 ] , a [ 4 ] a[1],a[2],a[4] a[1],a[2],a[4]

是每次加了二进制的低位 1 1 1

1 → 1 + 1 = 10 → 10 + 10 = 100 → 100 + 100 = 1000 > 111 1to1+1=10to10+10=100to100+100=1000>111 11+1=1010+10=100100+100=1000>111(停止)

查询:

查询 ∑ i = 1 7 a [ i ] → sumlimits_{i=1}^7{a[i]}to i=17a[i] 查询 c [ 7 ] + c [ 6 ] + c [ 4 ] c[7]+c[6]+c[4] c[7]+c[6]+c[4]

是每次去掉了二进制中的低位 1 1 1

111 → 111 − 1 = 110 → 110 − 10 = 100 → 100 − 100 = 0 111to111-1=110to110-10=100to100-100=0 1111111=11011010=100100100=0(停止)

l o w b i t lowbit lowbit 的实现 :

int lowbit(int x)
{
return x & -x;
}

我们知道,一个数的负数就等于对这个数取反 + 1 +1 +1

以二进制数 11010 11010 11010 为例: 11010 11010 11010 取反为 00101 00101 00101,加 1 1 1 后为 00110 00110 00110,两者相与便是最低位的 1 1 1

3. 一维树状数组

1. 单点修改,区间查询

P3374 【模板】树状数组 1

  • 单点修改

由刚才所说可知:

void update(int x, int k) //x 为要更新的数,k 为要加上的值,n 为数组大小
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i] += k;
}
}
  • 区间查询

其实也就是单点更新的逆操作。

要想求出 x ∼ y xsim y xy 相当于 1 ∼ y 1sim y 1y 减去 1 ∼ x − 1 1sim x-1 1x1

int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
int range_query(int x, int y)
{
return query(y) - query(x - 1);
}

  • A C    C o d e AC;Code ACCode
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 5e5 + 5;
int n, m, op, x, y;
int c[MAXN];
int lowbit(int x)
{
return x & -x;
}
void update(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i] += k;
}
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
update(i, x);
}
while (m--)
{
scanf("%d%d%d", &op, &x, &y);
if (op == 1)
{
update(x, y);
}
else
{
printf("%dn", query(y) - query(x - 1));
}
}
return 0;
}

2. 区间修改,单点查询

P3368 【模板】树状数组 2

  • 区间修改

这里用到了差分的思想

设原数组为 a a a,差分数组 d [ i ] = a [ i ] − a [ i − 1 ] d[i] = a[i] - a[i-1] d[i]=a[i]a[i1]

当给区间 [ l , r ] [l, r] [l,r] 加上 k k k 时, a [ l ] a[l] a[l] a [ l − 1 ] a[l-1] a[l1] 的差增加了 k k k a [ r + 1 ] a[r+1] a[r+1] a [ r ] a[r] a[r] 减少了 k k k,故只需将 d [ l ] d[l] d[l] 加上 k k k a [ r + 1 ] a[r+1] a[r+1] 减去 k k k 即可。

void update(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i] += k;
}
}
void range_update(int x, int y, int k)
{
update(x, k);
update(y + 1, -k);
}
  • A C    C o d e AC;Code ACCode
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 5e5 + 5;
int n, m, now, last, op, x, y, k;
int c[MAXN];
int lowbit(int x)
{
return x & -x;
}
void update(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i] += k;
}
}
void range_update(int x, int y, int k)
{
update(x, k);
update(y + 1, -k);
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &now);
update(i, now - last);
last = now;
}
while (m--)
{
scanf("%d%d", &op, &x);
if (op == 1)
{
scanf("%d%d", &y, &k);
range_update(x, y, k);
}
else
{
printf("%dn", query(x));
}
}
return 0;
}

3. 区间修改,区间查询

P3372 【模板】线段树 1

位置 x x x 的前缀和

∑ i = 1 x a [ i ] = ∑ i = 1 x ∑ j = 1 i d [ j ] sumlimits_{i=1}^{x}a[i]=sumlimits_{i=1}^{x}sumlimits_{j=1}^{i}d[j] i=1xa[i]=i=1xj=1id[j]

其中, d [ 1 ] d[1] d[1] 被用了 x x x 次, d [ 2 ] d[2] d[2] 被用了 x − 1 x-1 x1 … … …… 那么可以写出:

∑ i = 1 x ∑ j = 1 i d [ j ] = ∑ i = 1 x d [ i ] ⋅ ( x − i + 1 ) sumlimits_{i=1}^{x}sumlimits_{j=1}^{i}d[j]=sumlimits_{i=1}^{x}d[i]cdot(x-i+1) i=1xj=1id[j]=i=1xd[i](xi+1)

   = ( x + 1 ) ⋅ ∑ i = 1 x d [ i ] − ∑ i = 1 x d [ i ] ⋅ i qquadqquad =(x+1)cdotsumlimits_{i=1}^{x}d[i]-sumlimits_{i=1}^{x}d[i]cdot i   =(x+1)i=1xd[i]i=1xd[i]i

那么可以维护两个数组的前缀和:

一个数组是 c 1 [ i ] = d [ i ] c1[i]=d[i] c1[i]=d[i]

另一个数组是 c 2 [ i ] = d [ i ] ⋅ i c2[i]=d[i]cdot i c2[i]=d[i]i

  • 区间修改

对于 c 1 c1 c1 数组的修改同上对 d d d 数组的修改。

对于 c 2 c2 c2 数组的修改也类似,我们给 c 2 [ l ] c2[l] c2[l] 加上 l ⋅ x l cdot x lx,给 c 2 [ r + 1 ] c2[r + 1] c2[r+1] 减去 ( r + 1 ) ⋅ x (r + 1) cdot x (r+1)x

void update(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
c1[i] += k;
c2[i] += k * x;
}
}
void range_update(int x, int y, int k)
{
update(x, k);
update(y + 1, -k);
}
  • 区间查询

位置 x x x 的前缀和即: ( x + 1 ) ⋅ t 1 (x+1)cdot t1 (x+1)t1 数组中 x x x 的前缀和 − t 2 - t2 t2 数组中 x x x 的前缀和。

int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += (x + 1) * c1[i] - c2[i];
}
return res;
}
int range_query(int x, int y)
{
return query(y) - query(x - 1);
}
  • A C    C o d e AC;Code ACCode
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int n, m, a, last, op, x, y, k;
int c1[MAXN], c2[MAXN];
int lowbit(int x)
{
return x & -x;
}
void update(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
c1[i] += k;
c2[i] += k * x;
}
}
void range_update(int x, int y, int k)
{
update(x, k);
update(y + 1, -k);
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += (x + 1) * c1[i] - c2[i];
}
return res;
}
int range_query(int x, int y)
{
return query(y) - query(x - 1);
}
signed main()
{
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a);
update(i, a - last);
last = a;
}
while (m--)
{
scanf("%lld%lld%lld", &op, &x, &y);
if (op == 1)
{
scanf("%lld", &k);
range_update(x, y, k);
}
else
{
printf("%lldn", range_query(x, y));
}
}
return 0;
}

4. 二维树状数组

1.单点修改,区间查询

思路跟一维类似,只不过一层循环变两层

  • 区间查询

查询时要用到容斥原理

int query(int x, int y)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
for (int j = y; j; j -= lowbit(j))
{
res += c[i][j];
}
}
return res;
}
int range_query(int a, int b, int c, int d) //左上角 (a,b) 右下角 (c,d)
{
return query(c, d) - query(c, b - 1) - query(a - 1, d) + query(a - 1, b - 1);
}

2. 区间修改,单点查询

需要用到二维差分。

  • 区间修改

令差分数组
d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] d[i][j]=a[i][j] - a[i-1][j] - a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

当我们给一个 5 × 5 5times5 5×5 的矩阵最中间的 3 × 3 3times3 3×3 的矩阵加上 k k k 时,差分数组变成了这样:

0
0
0
0
0
0 +k
0
0 -k
0
0
0
0
0
0
0
0
0
0
0 -k
0
0 +k

这样修改差分数组,原数组就变成了:

0
0
0
0
0
0
k
k
k
0
0
k
k
k
0
0
k
k
k
0
0
0
0
0
0
void update(int x, int y, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
for (int j = y; j <= m; j += lowbit(j))
{
c[i][j] += k;
}
}
}
void range_update(int a, int b, int c, int d, int k)
{
update(a, b, k);
update(a, d + 1, -k);
update(c + 1, b, -k);
update(c + 1, d + 1, k);
}

3. 区间修改,区间查询

最最最最 恶心 color{White}colorbox{White}{恶心} 重要的部分终于来啦!

下面这个式子表示的是点 ( x , y ) (x, y) (x,y) 的二维前缀和:

∑ i = 1 x ∑ j = 1 y ∑ k = 1 i ∑ h = 1 j d [ h ] [ k ] sumlimits_{i=1}^{x}sumlimits_{j=1}^{y}sumlimits_{k=1}^{i}sumlimits_{h=1}^{j}d[h][k] i=1xj=1yk=1ih=1jd[h][k]

d [ 1 ] [ 1 ] d[1][1] d[1][1] 出现了 x ⋅ y xcdot y xy 次, d [ 1 ] [ 2 ] d[1][2] d[1][2] 出现了 x ⋅ ( y − 1 ) x cdot (y-1) x(y1) … … d [ h ] [ k ] …… d[h][k] d[h][k] 出现了 ( x − h + 1 ) ⋅ ( y − k + 1 ) (x-h+1)cdot(y-k+1) (xh+1)(yk+1) 次。

原式 = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ⋅ ( x + 1 − i ) ⋅ ( y + 1 − j ) =sumlimits_{i=1}^{x}sumlimits_{j=1}^{y}d[i][j]cdot(x+1-i)cdot(y+1-j) =i=1xj=1yd[i][j](x+1i)(y+1j)

      = ( x + 1 ) ⋅ ( y + 1 ) ⋅ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] − ( y + 1 ) ⋅ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ⋅ i − ( x + 1 ) ⋅ ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ⋅ j + ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ⋅ i ⋅ j quad ,=(x+1)cdot(y+1)cdotsumlimits_{i=1}^{x}sumlimits_{j=1}^{y}d[i][j]-(y+1)cdotsumlimits_{i=1}^{x}sumlimits_{j=1}^{y}d[i][j]cdot i-(x+1)cdotsumlimits_{i=1}^{x}sumlimits_{j=1}^{y}d[i][j]cdot j+sumlimits_{i=1}^{x}sumlimits_{j=1}^{y}d[i][j]cdot icdot j    =(x+1)(y+1)i=1xj=1yd[i][j](y+1)i=1xj=1yd[i][j]i(x+1)i=1xj=1yd[i][j]j+i=1xj=1yd[i][j]ij

我们要开 4 4 4 c c c 数组,分别维护:

d [ i ] [ j ] , d [ i ] [ j ] ⋅ i , d [ i ] [ j ] ⋅ j , d [ i ] [ j ] ⋅ i ⋅ j d[i][j],d[i][j]cdot i,d[i][j]cdot j,d[i][j]cdot icdot j d[i][j],d[i][j]i,d[i][j]j,d[i][j]ij

void update(int x, int y, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
for (int j = y; j <= m; j += lowbit(j))
{
c1[i][j] += k;
c2[i][j] += k * x;
c3[i][j] += k * y;
c4[i][j] += k * x * y;
}
}
}
void range_update(int a, int b, int c, int d, int k)
{
update(a, b, k);
update(a, d + 1, -k);
update(c + 1, b, -k);
update(c + 1, d + 1, k);
}
int query(int x, int y)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
for (int j = y; j; j -= lowbit(j))
{
res += (x + 1) * (y + 1) * c1[i][j] - (y + 1) * c2[i][j] - (x + 1) * c3[i][j] + c4[i][j];
}
}
return res;
}
int range_query(int a, int b, int c, int d)
{
return query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1);
}

P4514 上帝造题的七分钟

A C    C o d e AC;Code ACCode

#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 2050;
int n, m;
int c1[MAXN][MAXN], c2[MAXN][MAXN], c3[MAXN][MAXN], c4[MAXN][MAXN];
int lowbit(int x)
{
return x & -x;
}
void update(int x, int y, int k)
{
for (int i = x; i <= n; i += lowbit(i))
{
for (int j = y; j <= m; j += lowbit(j))
{
c1[i][j] += k;
c2[i][j] += y * k;
c3[i][j] += x * k;
c4[i][j] += x * y * k;
}
}
}
void range_update(int a, int b, int c, int d, int k)
{
update(a, b, k);
update(a, d + 1, -k);
update(c + 1, b, -k);
update(c + 1, d + 1, k);
}
int query(int x, int y)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
for (int j = y; j; j -= lowbit(j))
{
res += (x + 1) * (y + 1) * c1[i][j] - (x + 1) * c2[i][j] - (y + 1) * c3[i][j] + c4[i][j];
}
}
return res;
}
int range_query(int a, int b, int c, int d)
{
return query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1);
}
int main()
{
getchar();
scanf("%d%d", &n, &m);
char op;
int a, b, c, d, k;
while (~scanf("n%c%d%d%d%d", &op, &a, &b, &c, &d))
{
if (op == 'L')
{
scanf("%d", &k);
range_update(a, b, c, d, k);
}
else
{
printf("%dn", range_query(a, b, c, d));
}
}
return 0;
}

T H E    E N D mathcal{THE;END} THEEND

最后

以上就是欢喜白开水为你收集整理的_树状数组_1. 什么是树状数组?2. l o w b i t lowbit lowbit3. 一的全部内容,希望文章能够帮你解决_树状数组_1. 什么是树状数组?2. l o w b i t lowbit lowbit3. 一所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部