树状数组的用途
树状数组可以解决数量级较大的区间求和问题,区间求最值问题,区间修改、查询问题以及 求逆序对等应用。树状数组的时间复杂度 O(log2n),正常情况下远远快于线性时间 O(n)。
树状数组的原理
图 2 中,a 数组就是需要维护求区间和的数组,其实a 数组在求区间和的时候并没有太大的 意义,只是求区间最值的时候会有用到。我们先来关注一下储存和的 c 数组。 C8=c4+c6+c7+a8,c6=c5+a6…为什么要这么操作,因为这么操作更加的快捷,如果要查询前 1 到 8 内的区间和,只需要 4 个数相加就 ok,而为什么要选择这 4 个数,因为有一种操作刚 好可以将没一个数按照我们既定的规则将每一位的数加入到 c 数组中。 看一下效果图:
然后看一下标准化的图一,很容易看出其中的二分的思想,其实树状数组就是巧妙的利用了 二分将线性时间降为了 log2n。其余的东西请同学们自行理解。
创建树状数组 根据我们刚才的思想,数组 c 需要记录前 n 项的和,那么就需要将当前项的数加入到后面的 每一个需要用到的数组中,此时实用 lowbit 函数来完成此次操作。 一定要注意数组的下标要从 1 开始,而不是 0.
int lowbit(int x)//巧妙地利用lowbit()函数
{
return x&(-x);
}
void add(int x,int y)//创建一个树状数组,X为下标,y为数值
{
while(x<=n)
{
c[x]+=y;
x+=lowbit(x);
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)//注意数组下标要从1开始
{
scanf("%d",&a[i]);
add(i,a[i]);
}
for(int i=1;i<=n;i++)
{
printf("%d\n",c[i]);
}
}
return 0;
}
此时完成树状数组的创建。
区间求和
巧妙利用 lowbit,区间求和是树状数组的一个比较重要的应用。 C数组存的就是区间段的和, 所以求和时只需对 C 数组进行操作,另外求和只是求区间[1,x]的和,如要求[x,y]的和,求出 [1,x-1],[1.y]再相减就 ok。
int read(int x)
{
int ans=0;
while(x)
{
ans+=c[x];
x-=lowbit(x);//lowbit()函数之前有提到
}
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)//注意数组下标要从1开始
{
scanf("%d",&a[i]);
add(i,a[i]);//add()函数之前有提到
}
int x,y;
while(cin>>x>>y)
{
printf("%d\n",read(y)-read(x-1));//计算从x到y的区间和
}
}
区间修改
有时候需要修改一段区间的值,然后再进行区间求和,此时就不能执行以前 C 数组,只能换 一种方法来求。 我们先建立一个数组来储存需要输入的序列,然后创建一个差分数组来保存原数组中每一项 与前一项的差值,这样的话第 i 项就是从第 1 项加到第 i 项的和,我们来看一下前 i 项的和, a[1]+a[2]+...+a[n] = (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) = n*c[1] + (n-1)*c[2] +... +c[n] = n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])
那么我们就维护一个数组 del2[n],其中 del2[i] = (i-1)*c[i]
然后就成了两个数组的区间求和问题。 代码实现:
long long Sigma(long long *arra,long long x) //数组中 1 到 x 的和
{
long long ans=0;
while(x)
{
ans+=arra[x];
x-=lowbit(x);
}
return ans;
}
void updata(long long *c,long long x,long long y) //在数组 c 中后 x 加入 y
{
while(x<=n)
{
c[x]+=y;x+=lowbit(x);
}
}
for(i=1;i<=10;i++)
{
cin>>Map[i];
updata(del1,i,Map[i]-Map[i-1]);
updata(del2,i,(i-1)*(Map[i]-Map[i-1]));
}
while(cin>>q)
{
if(q%2)
{
cout<<"区间修改,输入要修改的区间以及要添加的值:"<<endl;
cin>>x>>y>>z;
updata(del1,x,z);
updata(del1,y+1,-z);
updata(del2,x,z*(x-1));
updata(del2,y+1,-z*y);
}
else {
cout<<"区间查询,输入要查询的区间:"<<endl;
cin>>x>>y;
long long sum1=(x-1)*Sigma(del1,x-1)-Sigma(del2,x-1);
long long sum2=y*Sigma(del1,y)-Sigma(del2,y);
printf("%lld\n",sum2-sum1);
}
}
效果图:
void query_max(int l,int r)
{
long long ans=Map[r];
while(true) {
ans=max(ans,Map[r]);
if(r==l)
break;
for(r-=1;r-l>=lowbit(r);r-=lowbit(r))
{
ans=max(ans,maxx[r]);
}
}
cout<<"最大值为:"<<ans<<endl;
}
void query_min(int l,int r)
{
long long ans=Map[r];
while(true) {
ans=min(ans,Map[r]);
if(r==l)
break;
for(r-=1;r-l>=lowbit(r);r-=lowbit(r))
{
ans=min(ans,minn[r]);
}
}
cout<<"最小值为:"<<ans<<endl;
} //主函数
for(i=1;i<=10;i++) {
maxx[i]=Map[i];
for(int j=1;j<lowbit(i);j<<=1)
{
maxx[i]=max(maxx[i],maxx[i-j]);
}
}
for(i=1;i<=10;i++) {
minn[i]=Map[i];
for(int j=1;j<lowbit(i);j<<=1)
{
minn[i]=min(minn[i],minn[i-j]);
}
}
while(cin>>x>>y) {
query_max(x,y);
query_min(x,y);
}
求最值
参考博客:https://www.cnblogs.com/ambition/archive/2011/04/06/bit_rmq.html
代码实现:
效果图:
The end……