这篇文章写的很详细:
链接
这个b站上的视频讲的也很透彻:
链接
什么是树形数组:
树形数组是一种用来维护前缀和的工具,上图中序列上的就是树形数组,它虽然长得像个树,但是确是一个数组,比如上面的t[4]的值就是1-4的前缀和,其他元素以此类推。
add操作实现原理:
ask操作实现原理:
下面是区间查询+单点修改(1665and1666)的代码:
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
inline void write(ll x)
{
if (x < 0) x = ~x + 1, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
ll ans=0;
for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层
{
ans+=tree[j];
}
return ans;
}//ask操作,求1-r的前缀和
void add(ll p,ll w)
{
for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作
{
tree[j]+=w;
}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树形数组上是让含有dat[p]的项都加w
int main()
{
n=read();
q=read();
for(int i=1;i<=n;++i)
{
dat[i]=read();
}
for(int i=1;i<=n;++i)
{
ll k=lowbit(i);
for(int j=1;j<=k;++j)
{
tree[i]+=dat[i-k+j];//树状数组储存
}
}
for(int i=1;i<=q;++i)
{
op=read();
p_l=read();
w_r=read();
switch(op)
{
case 1:
{
write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
printf("\n");
break;
}
case 2:
{
dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值
add(p_l,w_r);
break;
}
}
}
return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/
下面是另一种构建树状数组的方法(这种比较好,上面那种是我自己按照意思写的):
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
inline void write(ll x)
{
if (x < 0) x = ~x + 1, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
ll ans=0;
for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层
{
ans+=tree[j];
}
return ans;
}//ask操作,求1-r的前缀和
void add(ll p,ll w)
{
for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作
{
tree[j]+=w;
}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树状数组上是让含有dat[p]的项都加w
int main()
{
n=read();
q=read();
for(int i=1;i<=n;++i)
{
dat[i]=read();
add(i,dat[i]);//树状数组储存
}
for(int i=1;i<=q;++i)
{
op=read();
p_l=read();
w_r=read();
switch(op)
{
case 1:
{
write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
printf("\n");
break;
}
case 2:
{
dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值
add(p_l,w_r);
break;
}
}
}
return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/
下面是区间修改+区间查询(1667and1668)的代码:
听说这种题用线段树比较好解= =,我用树状数组也就是套了个公式(蒟蒻瑟瑟发抖)。
其中tree1维护了差分数组b[i]的前缀和,tree2维护了i*b[i]的前缀和,sum是数据数组a的前缀和。代码中没有写出差分数组b是因为可以利用树状数组不用写出而直接求其前缀和。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[1000005],sum[1000005],tree1[1000005],tree2[1000005];
ll n,q,op,l,r,w;
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
inline void write(ll x)
{
if (x < 0) x = ~x + 1, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
return a&(-a);
}
ll ask1(ll r)
{
ll ans=0;
for(int i=r;i>=1;i-=lowbit(i))
{
ans+=tree1[i];
}
return ans;
}
ll ask2(ll r)
{
ll ans=0;
for(int i=r;i>=1;i-=lowbit(i))
{
ans+=tree2[i];
}
return ans;
}
void add1(ll p,ll w)
{
for(int i=p;i<=n;i+=lowbit(i))
{
tree1[i]+=w;
}
}
void add2(ll p,ll w)
{
for(int i=p;i<=n;i+=lowbit(i))
{
tree2[i]+=w;
}
}
int main()
{
n=read();
q=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
if(i==1) sum[i]=a[i];
else sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=q;++i)
{
op=read();
switch(op)
{
case 1:
{
l=read();
r=read();
write((sum[r]+(r+1)*ask1(r)-ask2(r))-(sum[l-1]+l*ask1(l-1)-ask2(l-1)));
printf("\n");
break;
}
case 2:
{
l=read();
r=read();
w=read();
add1(l,w);
add1(r+1,-w);
add2(l,l*w);
add2(r+1,-(r+1)*w);
break;
}
}
}
return 0;
}