今天给大家讲本周新学的算法—-树状数组
一.题目引入
本题看着很简单,大多数人想到的都是暴力,枚举每一个数,每一个区间,但是实际这样是超时的,要想本题被接受就需要用到我们本节的算法——树状数组。
二.算法分析
理解:数状数组,顾名思义就是数组,只是在树状数组中的每一个元素是由用非负整数在二进制表示下最低位1及后面的数字的数值组成的
作用:单点修改,区间查询(本节需要掌握的);区间修改,单点查询;区间修改,区间查询。
三.前置知识
刚才讲了树状数组关键不同在于我们需要把数组中的每个元素用一个二进制表示,这个二进制数就是非负整数n在二进制下最低位1及后面的数字,案列lowbit(44)=lowbit(101100)=lowbit(100)=4,实现上述案列,我们需要先将二进制取反+1,再按位与(&)。
二进制取反:将每一位变成相反的;
加1:二进制加法逢2进1;
按位与(&):只有两个数的二进制同时为1,结果才为1,否则为0。
过程如图,大家可以一步一步试一下
代码实现:(由于计算机使用的是补码所以二进制数取反+1就是-x)
int lowbit(int x)
{
return x&-x;
}
四.思想与实现
1.对于一个序列,我们在其上建立一个森林结构
2.计算每个节点覆盖的长度,将每个节点t[x]的x转化为二进制
3.我们会发现每一层的末尾的零的个数都是相同的,且t[x]节点覆盖的长度就是lowbit(x)
4.再进一步发现每一个节点的父节点就是t[x+lowbit(x)],所以我们可以完成题目的第一问,给需要加的数x加上k,这里我们给第x个数加了k,所以需要更新后面的每个t[x]
void add(int x,int k)
{
while(x<=n)
{
tree[x]+=k;
x+=lowbit(x);
}
}
5.为了方便我们求第二问区间和,对上上图再次观察,如果要求我们求前7项的和,如何树状数组来维护呐?我们发现向左上找上一个节点,只需要将下标减去lowbit(这个节点的下标),并且每个都加上其节点的值
6.通过5.我们学会了如何通过树状数组求任意的前n项和,那么对于我们的第二问求(x,y)的区间和只需要求出前y项的和,再求出前x-1项的和(如何不懂为什么是x-1项的和可以参考我的第一篇文章前缀和)
代码实现:
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
if(a==2) {
cout<<sum(c)-sum(b-1)<<endl;
}
}
AC代码
#include <bits/stdc++.h>
using namespace std;
int n,m,tree[2000010];
int lowbit(int k)
{
return k & -k;
}
void add(int x,int k)
{
while(x<=n)
{
tree[x]+=k;
x+=lowbit(x);
}
}
int sum(int x)
{
int ans=0;
while(x!=0)
{
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int a;
cin>>a;
add(i,a);
}
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(a==1)
add(b,c);
if(a==2)
cout<<sum(c)-sum(b-1)<<endl;
}
return 0;
}