树状数组的用途:
例:给定一个大小为n的数组,要求在数组中对数进行操作。
操作规则:
1.将数组中某个数进行修改(加减运算)
2.将数组从下标a到下标b这个区间的数进行修改(都同时进行加或减运算)
3.查询修改后的某个数或者某个区间
其实,我想读者也应该猜到了一种做法:“前缀和”
当然,这类问题可以用前缀和来做,但是前缀和做法最坏的情况下的复杂度的会达到O(n^2),当然,这对于大数据而言,是肯定不能接受的,那么我们就需要用“树状数组”的知识来解决这类问题。
接下来我们来逐步引入树状数组的概念!
前置知识点:1.前缀和 2.差分数组 3.lowbit()函数
1:前缀和
引入一个问题:给定一个数组 a[6]={0,1,2,3,4,5},求区间[1,4]所有元素的和(区间和)?
常规做法:从数组下标1枚举到下标4,依次进行加法,求得最终值10(区间和)
前缀和做法:sum[6]={0,1,3,6,10,15} 最终值为:sum[4]-sum[0]=10(区间和)
不了解前缀和的巨巨们,可能会感觉很神奇,sum数组是如何得到的?为什么是sum[4]-sum[0]的值就是区间和?
(会前缀和的巨巨们可忽略本段)
{
sum数组求法:
void SUM(int a[],int n)
{
sum[0]=0;
for(int i=1;i<n;i++)
sum[i]=sum[i-1]+a[i];//简单的递推式,不会的话,可以自己手动模拟一下过程哦!
}
就拿以上的例子来算区间[2,4]的区间和
我们可以知道,常规做法是从下标1枚举到下标4,再依次加法:2+3+4=9;
由图中可以知道,sum[1]是区间[0,1]区间和,sum[n]是区间[0,n]区间和,那么我们要算区间[2,4]的区间和,就需要知道区间[0,4]和区间[0,1]的区间和。那么肯定有读者会问,为什么不是区间[0,2]的区间和?由以下图解可知!
由此我们得到一个算前缀和区间的公式:求区间[L,R]的区间和:ans=sum[R]-sum[L-1]
代码实现:
int qujianhe(int L,int R)
{
return sum[R]-sum[L-1];
}
好耶!然后我们就可以用前缀和求任意区间和了
例题:给定一个数组:a[5]={1,3,5,7,9},求区间[2,4]的区间和
代码实现:
#include<stdio.h>
const int N=10001;
int a[N],sum[N];
int n;
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sum[0]=1;//注意这里的sum[0],应该是等于数组中的第一位数
for(int i=1;i<n;i++)
sum[i]=sum[i-1]+a[i];
printf("%d",sum[4]-sum[1]);
}
以上就是前缀和的概念,很简单吧!(我相信巨巨们是不在话下的....0.0)
}
2:差分数组
咱们同样引入一个问题:给定一个数组,对数组的某个区间同时加一个数或者减一个数,求某个数修改后的值(所属修改区间内)。
常规做法:枚举区间,然后同时进行加减操作,最后输出想要查询某个位置修改后的值
差分数组:求下标为5的位置修改后的值:sum[5](注意,这里是差分数组的前缀和)
(会差分数组的巨巨们可忽略本段)
{
差分数组的实现:
int Chafen(int a[],int n)
{
d[0]=0;
for(int i=1;i<n;i++)
d[i]=a[i]-a[i-1];
}
然后问题来了,给定差分数组有什么用呢?
问的好!(自问自答-0.0-)
如果我们想改变一个区间的值(同时变化相等的量),那么它的差分数组是不会发生改变的,但是它的前缀和就会发生改变,一个数发生改变,前缀和就会改变一次,一个区间里面有n个数发生改变,前缀和就会对应每个数改变n次,从而得到新的前缀和,这样是非常麻烦的,那么差分数组所解决的问题就是查询某个变化的值,它同时也运用了前缀和的思想。
仔细看上图:会发现一个神奇的现象
我们会发现:原数组每个位置上的值,就是差分数组里,每个位置的前缀和
举个例子:原数组 a[3]=4 等于 差分数组:sum[3]=0+1+2+1=4
至于解释,跟上面前缀和的解释方法是一样的,这里,就请读者自行证明吧,因为本节的重点是lowbit()函数的用法
对于对区间的修改,其差分数组在修改的区间的差值是不会变的,但是问题来了,对区间进行修改,修改区间的差分是不会变的,但是会影响区间结束后 后面区间的差分,那么如何来进行阻止这一变化呢?
问得好!(再次自问自答,嘻嘻嘻...)
区间修改的代码实现:
void qujianxiugai(int L,int R,int k)//分别代表,左区间,右区间,修改的值
{
d[L]+=k;
d[R+1]-=k;
}
very good!
例题:给定一个数组a[6]={0,1,3,4,7,11},对区间[1,3]进行加2,修改后的区间,下标为2的值
常规做法:略...
差分数组+前缀和:代码实现
#include<stdio.h>
const int N=10001;
int a[N],sum[N],d[N];
int n;
void qujianxiugai(int L,int R,int k)
{
d[L]+=k;
d[R+1]-=k;
}
void Chafen(int a[],int n)
{
d[0]=0;
for(int i=1;i<n;i++)
d[i]=a[i]-a[i-1];
}
int presum(int x)
{
sum[0]=0;
for(int i=1;i<n;i++)
sum[i]=sum[i-1]+d[i];
return sum[x];
}
int main()
{
printf("输入数组a[6]\n");
printf("数组大小为:");
scanf("%d",&n);
printf("a数组:");
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
int L,R,k,x;
printf("输入要修改的区间:");
scanf("%d%d",&L,&R);
printf("输入区间变化的值:");
scanf("%d",&k);
printf("输入需要查询变化后数组下标:");
scanf("%d",&x);
Chafen(a,n);
qujianxiugai(L,R,k);
printf("所查询的值为:");
printf("%d",presum(x));
}
以上就是差分数组的概念,很简单吧!(我相信巨巨们是不在话下的....0.0)
}
3.lowbit()函数
先看lowbit函数的代码:
int lowbit(int x)
{
return x&(-x);
}
lowbit函数的含义即为:x(二进制)最低位1后面的0组成的数
例如:
5(二进制:101)最低位1后面0的个数为0,则lowbit(5)=2^0=1(十进制)
12(二进制:1100) 最低位1后面0的个数为2,则lowbit(12)=2^2=4(十进制)
如果x为负数怎么办?其实lowbit(-x)=lowbit(x)
好的,咱们学完了前置知识,现在来具体介绍树状数组,经过前缀知识点的学习,大家也许发现了,树状数组其实是前缀和思想的一种优化。
树状数组的概念:
树状数组就是基于一种二进制思想的数据结构,基本用途是维护序列前缀和。对于给定的序列a,设数组数组为c,则c[x]保存在序列a的区间[x-lowbit(x)-1,x]中所有数的和。
主要有以下两个基本操作:
1.单点修改,修改序列a中的某个元素
2.区间查询,查询序列a中区间[1,x]的所有数的和
树状数组图解:
以上树状数组的截图选自:树状数组
总结:其实树状数组的操作,无非就是+-lowbit(x),如果读者还是不懂的话,可以点击以下链接去看更为具体的树状数组讲解。
另外,我再给予读者两道树状数组的模板题(附题解)
例题1:https://www.luogu.com.cn/problem/P3374
题解代码:
#include<bits/stdc++.h>
using namespace std;
const int N=500001;
int c[N],n,m;
long long ans;
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=k;
}
void query(int l,int r)
{
for(int i=r;i;i-=lowbit(i))
ans+=c[i];
for(int i=l-1;i;i-=lowbit(i))
ans-=c[i];
cout<<ans<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int number;
cin>>number;
add(i,number);
}
while(m--)
{
int a,x,y;
cin>>a>>x>>y;
if(a==1) add(x,y);
else
{
ans=0;
query(x,y);
}
}
}
例题2:https://www.luogu.com.cn/problem/P3368
题解代码:
#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int a[N],c[N],d[N];
int n,m;
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int k)
{
for(int i=pos;i<=n;i+=lowbit(i))
c[i]+=k;
}
void query(int pos)
{
long long ans=0;
for(int i=pos;i;i-=lowbit(i))
ans+=c[i];
cout<<ans<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
d[i]=a[i]-a[i-1];
add(i,d[i]);
}
while(m--)
{
int a,x,y,k;
cin>>a;
if(a==1)
{
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else
{
cin>>x;
query(x);
}
}
}
欢迎各位巨巨评论区留言哦!
创作不易,各位读者大大点个赞再走吧(点个关注就更好了q.q..)