树状数组
树状数组,就有点把二进制和分治思想结合了一下
树状数组这个数据结构支持单点增加,区间求和
树状数组就是一个运用前缀和的高级数据结构,它满足如下几个性质:
1.每一个节点,c[x]保存都会保存一个以这个点为根的子树的所有节点的叶子节点的和
2.每一个节点,c[x]的子节点个数等于lowbit的位数
3.除了根节点之外,每一个内部节点c[x]c[x]的父亲就是c[x+lowbit(x)那么他的儿子就是c[x-lowbit()x]
lowbit
首先我们线了解一下什么是lowbit,lowbit的具体的含义很难理解,但是他的值却很好求,比如我们设一个数是x,那么就有lowbit(x)=x&-x但是他具体是什么意思,我认为没有必要进行研究
经过一个月后,其实-x是x的补码,与他进行按位与,那么就是最低位1的位置,也就是从右往左的第一个为1的位置
查询前缀和
就是说序列a第1~x个数的和,按照我们刚才提出的方法,就是求出x的二进制表示每一个等于1的位,把[1,x]分成若干个区间,然后把每一个区间和都保存数组c中
int ask(int x)
{
int ans=0;
for(;x;x-=x&x)//lowbit不断变化,往上调用儿子,往上传递
{
ans+=c[x];//c数组保存叶子节点
}
return ans;
}
当然,如果要查询序列a的区间的所有的和,就需要计算ask®-ask(l-1)
单点增加
就是说给一个序列中加上一个数a[x]加上y,同时正确维护序列的前缀和,这句话的后半段很重要,必须要按满足前缀和,我们要注意进行更新,不断地往上更新
我们可以利用这个单点增加来进行构建一个树状数组,因为我们可以感觉这个进行每一个点的单点增加
void add(int x)
{
int ans=0;
for(;x;x-=x&x) ans+=c[x];//向上更新
return ans;
}
树状数组其实还会结合一种东西叫做逆序对,我们也可以根据树状数组进行创建一个逆序对:
1.在序列a中的数值范围内建立树状数组,初始化位0
2.然后倒序扫描给定的序列a,对于每一个数:
(1)查询树状数组[1,a[i]-1]累计加到答案
(2)执行单点增加操作
for(int i=n;i;i--)
{
ans+=ask(a[i]-1);
add(a[i],1);
}
cout<<<ans<<<endl;
下面来一个综合代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
int n,m;
int a[500010];//数列
int tree[500010]//前缀和数组
int q,x,y;
int lowbit(int x)
{
return x&-x;
}
void update(int x,int k)//将第x数加上k
{
while(x<=n)
{
tree[x]+=k;
x+=lowbit(x);//往上传递信息
}
}
void build()
{
for(int i=1;i<=n;i++)
{
update(i,a[i]);//利用单点增加建树
}
}
int qian(int x)//x的前缀和
{
int sum=0;//每次 都要清空
while(x)
{
sum+=tree[x];
x-=lowbit(x);//往下传递
}
return sum;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
build();
for(int i=1;i<=m;i++)
{
cin>>q>>x>>y;
if(q==1)
update(x,y);
else
cout<<qian(y)-qian(x-1);
}
return 0;
}
树状数组是一种高效的数据结构,结合了二进制和分治思想,支持单点增加和区间求和。它通过前缀和性质,能够快速查询和更新序列的和。在代码实现中,`lowbit`用于确定节点更新路径,`add`和`ask`函数分别用于单点增加和查询操作。逆序对问题也可借助树状数组解决。提供的综合代码展示了树状数组的构建和使用过程。

2357

被折叠的 条评论
为什么被折叠?



