第一行——咕咕咕。
第二行——本来没想写树状数组的,因为在四月就学会了而且做了一些题,觉得用处不大。直到——我又遇到树状数组的题而且我不会做了我哭辽,我对树状数组一点印象都没了,就三个月没做树状数组的题啊!!!!!于是愉快补学+补博客,以免将来再忘记+不好找板子。
第三行——查询和修改都是log(n)
以下为正文:
一、一维树状数组
1.树状数组:对于数组A1~An,我们用C1 = A1,C2 = A1+A2,C3 = A3,C4 = A1+A2+A3+A4……依次类推。凡是下标末尾为0的(二进制),有k个零即为前2^k个数的和,下标为2的幂的为前面所有数的和。lowbit返回的是最右边的第一个1的值(二进制)。C即为一个树状数组。
因为我们是按二进制末位是否为1储存,所以在处理时需要找到哪个C存了相应的A来进行修改,要用到lowbit函数。其他详细解释都在代码里。
2.多用于:①.单点修改,区间查询(区间和之类的)②.区间修改,单点查询
3.其他:维护和查询的时间复杂度都为logN。据说能用树状数组做的都能用线段树做,用线段树能做的树状数组不一定能做。但我不会线段树。
练习题目:
1.洛谷P3374(单点修改区间查询)(模板)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
ll c[1000005] = {0};//初始化为0
int lowbit(int x)
{
return (x)&(-x);
}//该操作可以得到2^k次方的值。
//只保留最低位的1(二进制下),即从左往右返回第一个1(ex:1101——>1)
void update(int x,int y,ll z)
{
while(x <= y)
{
c[x] += z;
x += lowbit(x);//往后跳的位数(哪一位有需要更改的元素)
}
}//x表示点,y表示共有几个点,z表示要更改的值
ll sum(int r)
{
ll tot = 0;
while(r > 0)
{
tot += c[r];
r -= lowbit(r);//往前跳的位数
}
return tot;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i)
{
ll a;
scanf("%lld",&a);
update(i,n,a);
}
for(int i = 0; i < m; ++i)
{
int op;
scanf("%d",&op);
if(op == 1)
{
int x;
ll y;
scanf("%d%lld",&x,&y);
update(x,n,y);
}
else if(op == 2)
{
int x,y;
scanf("%d%d",&x,&y);
ll res = sum(y) - sum(x-1);//求区间值
printf("%lld\n",res);
}
}
return 0;
}
2.洛谷P3368(区间修改单点查询)(模板)
上面就单纯正向思维,这道题运用了差分的思想。令B[1] = A[1],B[2] = A[2]-A[1],B[3] = A[3]-A[2]……我们可以看出,A[x] = B[1]+B[2]+……+B[x]。所以,我们可以用B来进行A中的区间修改,但只改B中两个点的值,C进行维护B的和,即C为树状数组。
其实就是相当于多了个预处理A为B。
哎不对啊,用不着B我为什么要写一个函数维护B的值??我是个傻的
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
ll b[500005] = {0};
ll a[500005] = {0};
ll c[500005] = {0};
int n;
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,ll z)
{
while(x <= y)
{
c[x] += z;
x += lowbit(x);
}
}
void add(int x,int y,ll z)
{
b[x] += z;
b[y+1] -= z;
}
ll chazhao(int x)
{
ll res = 0;
while(x > 0)
{
res += c[x];
x -= lowbit(x);
}
return res;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i)
{
scanf("%lld",&a[i]);
b[i] = a[i] - a[i-1];
update(i,n,b[i]);
}
for(int i = 0; i < m; ++i)
{
int op;
scanf("%d",&op);
if(op == 1)
{
int x;
int y;
ll z;
scanf("%d%d%lld",&x,&y,&z);
add(x,y,z);
update(x,n,z);
update(y+1,n,-z);
}
else if(op == 2)
{
int x;
scanf("%d",&x);
ll res = chazhao(x);
printf("%lld\n",res);
}
}
return 0;
}
二、二维树状数组
类似一维数组,只不过从求一段变成了求一个矩形区域。也同样是单点更新区间查询+区间更新单点查询。
题目:一个由数字构成的矩阵,对矩阵里的某个数加上一个整数,查询某个子矩阵里所有数字的和,对每次查询,输出结果。
用树状数组C[x][y]记录右下角为x,y,高为lowbit(x), 宽为 lowbit(y)的矩阵和(就跟一维同理,为2的幂就全部)
好了下午集训时间到了下班了回家喽留个坑,晚上写。好了六点半了我回来了。
1.Gym237040E(单点更新区间查询)(模板)
思路:就跟一维的一样,注意减重了再加上就行。最后求和ans那里不一定要这样加减,只要把重复减掉的加上就可以了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long
using namespace std;
ll c[4100][4100] = {0};
int n,m;
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))
c[i][j] += k;
}
ll add(int p,int q)
{
ll res = 0;
while(p)
{
int z = q;
while(z)
res += c[p][z],z -= lowbit(z);
p -= lowbit(p);
}
return res;
}
int main()
{
int op;
scanf("%d%d",&n,&m);
memset(c,0,sizeof(c));
while(~scanf("%d",&op))
{
if(op == 1)
{
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
update(x,y,k);
}
else if(op == 2)
{
int z1,z2,r1,r2;
scanf("%lld%lld%lld%lld",&z1,&z2,&r1,&r2);
ll ans = add(r1,r2) - add(r1,z2-1) - add(z1-1,r2) + add(z1-1,z2-1);
printf("%lld\n",ans);
}
}
return 0;
}
2.(区间更新单点查询)这个我还没遇到题,等遇到了再放上来。
好了接下来就是线段树了qwq
欢迎指出错误qwq