「学习笔记」一维树状数组
树状数组是一种查询和修改都是 的数据结构
树状数组可以做到单点修改区间查询,单点查询区间修改和区间修改区间查询
树状数组也分一维和二维
由此可以看出
要时刻记着每个点是怎么来的,否则后面容易混!!!
设节点编号为 ,那么,它所管辖的区域就是 ( 是 的二进制形式的末尾 的个数) 子节点找父节点就是加上最后一个
——
——
——
那现在问题来了,怎么求最后一个 ?
通常,我们把它叫做
计算方法 n & (-n)
&
: 位运算符,按位与,只有参与运算的两个二进制数所对应的二进制位均为 ,结果对应位才为 ,否则为
ll lowbit(ll x)
{
return x&(-x);
}
单点修改,区间查询:#
问题:在 位置加上 ,求 ,的区间和。
如果 位置加上 ,那么,它的父节点也要加上 ,查询父节点位置前面已经提到了,这个操作叫单点修改。
单点修改代码:
void add(ll x,ll y)//数组x位置加y
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;//逐层向上修改(毕竟子节点改了,父节点也要改)
}
}
建树的过程,就可以看作是单点修改。
区间查询,就是找类兄弟
( 可以看作前缀和)
发现了吗,找类兄弟就是减去最后一个 ,直至没有 为止
区间查询代码:
ll sum(ll x)
{
ll ans=0;
for(rll i=x;i;i-=(lowbit(i)))
{
ans+=s[i];
}
return ans;
}
单点修改,区间查询完整代码(有注释):
#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,a;
ll s[1000100];
void add(ll x,ll y)//数组x位置加y
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;//逐层向上修改(毕竟子节点改了,父节点也要改)
}
}
ll sum(ll x)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=s[i];//求和,找类兄弟
}
return ans;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(rll i=1;i<=n;++i)
{
scanf("%lld",&a);
add(i,a);//建树过程,把操作之前的树上的点都看作是0
}
for(rll p,l,r,i=1;i<=m;++i)
{
scanf("%lld%lld%lld",&p,&l,&r);
if(p==1)
{
add(l,r);//在l位置加上r
}
else
{
printf("%lld\n",sum(r)-sum(l-1));//查询区间[l-r]
}
}
return 0;
}
区间修改,单点查询:#
区间修改主要运用差分思想
注意,后面的内容必须会差分才可以看懂,不会差分的请止步。
区间修改代码(差分):
void add(ll x,ll y)
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;
}
}
单点查询,运用差分求和的思想, 的差分和就是第i个元素的数值
单点修改代码:
ll sum(ll x)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=s[i];//利用差分的思想求和,1-i的差分数组的和就是第i个数的值
}
return ans;
}
区间修改,单点查询完整代码(有注释):
#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,last,a;
ll s[1000100];
void add(ll x,ll y)//差分数组x的位置加y
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s[i]+=y;
}
}
ll sum(ll x)//求第x个数的数值
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=s[i];//利用差分的思想求和,1-i的差分数组的和就是第i个数的值
}
return ans;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(rll i=1;i<=n;++i)
{
scanf("%lld",&a);
add(i,a-last);//记录差分数组
last=a;//更新last,记录差分
}
for(rll p,l,r,j,i=1;i<=m;++i)
{
scanf("%lld",&p);
if(p==1)
{
scanf("%lld%lld%lld",&l,&r,&j);//在区间[l-r]中,每一个元素加j
add(l,j);//差分数组s[l]+j
add(r+1,-j);//差分数组s[r+1]-j
}
else
{
scanf("%lld",&j);
printf("%lld\n",sum(j));//求点j的值,利用差分求和
}
}
return 0;
}
区间修改,区间查询:#
这里的区间修改依然是差分思想,但求的是区间和,因此,与上面的有不同
区间和就是这个区间的数都加一遍, 是差分数组,差分数组从 到 的和就是
这里要自己亲自加一遍试试,全加起来,就会发现, 被加的个数有规律,最后用分配律,就是最终公式了。
公式一定要记住!!!
由此可见,区间修改,区间查询需要多维护一个 的和值区间修改代码:
void add(ll x,ll y)//差分数组x位置的数加y
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s1[i]+=y;//记录差分
s2[i]+=x*y;//记录差分的和
}
}
区间查询代码:
ll sum(ll x)//求前缀和(准确来说也不是前缀和,但性质差不多)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=(x+1)*s1[i]-s2[i];//根据公式计算
}
return ans;
}
scanf("%lld%lld",&l,&r);//求区间(l-r)的和
printf("%lld\n",sum(r)-sum(l-1));//前缀和计算
区间修改,区间查询完整代码(有注释):
#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,a,last;
ll s1[1000100],s2[1000100];
// s1 记录差分树状数组(公式第一项) s2 记录差分数组×i的和 (公式第二项)
void add(ll x,ll y)//差分数组x位置的数加y
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
s1[i]+=y;//记录差分
s2[i]+=x*y;//记录差分的和
}
}
ll sum(ll x)//求前缀和(准确来说也不是前缀和,但性质差不多)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
ans+=(x+1)*s1[i]-s2[i];//根据公式计算
}
return ans;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(rll i=1;i<=n;++i)
{
scanf("%lld",&a);
add(i,a-last);//a-last 计算差分
last=a;//更新last,使得记录的都是后一个数和前一个数的差
}
for(rll p,l,r,j,i=1;i<=m;++i)
{
scanf("%lld",&p);//判断是修改还是查询
if(p==1)
{
scanf("%lld%lld%lld",&l,&r,&j);//l-r区间的元素加j
add(l,j);//差分数组s1[l]+j
add(r+1,-j);//差分数组s1[r+1]-j
}
else
{
scanf("%lld%lld",&l,&r);//求区间(l-r)的和
printf("%lld\n",sum(r)-sum(l-1));//前缀和计算
}
}
return 0;
}
喜欢的话,还请支持一下吧,写的不好,dalao 勿喷QWQ
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/16417679.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】