浅谈树状数组
P.S. 树状数组之前认为难以理解,但是看了这个之后,恍然大悟,以下题目来自洛谷#
先三连+%up为敬
问题P3374:给你n个数,要进行k次单点修改和区间查询的操作#
给出一个表来对比一下暴力和树状数组:
做法 | 修改复杂度 | 查询复杂度 |
---|---|---|
朴素暴力 | ||
树状数组 |
借百度的图讲一下:
令这棵树的结点编号为,...,数组编号为,...
所以:
=
=+
=
=+++
……
发现性质:设节点编号为,那么这个节点管辖的区间为(其中为二进制末尾0的个数)个元素
因为这个区间最后一个元素必然为,
比如说,我们要求,可以得到
=++
把4转换为2进制,=
然后就是区间查询
假如我们要查询++...+的值就是要查询这几个区间的和的:
所以++...+=++,只用把3个值加起来就行了
=
操作 | 1 | 2 | 3 |
---|---|---|---|
抹去二进制的最后一个1 | |||
转化成十进制 | 13 | 12 | 8 |
管辖的范围 |
神奇的发现:++恰好等于13#
由于每次最多抹掉个0,也就是最多有次查询,复杂度为#
接下来是单点修改
假如要修改的节点编号为
由于数组储存的是前缀和,所以要修改所有包含的区间
当=5时,就是要修改这几个区间的值:
不难发现,对于单点修改的操作,其实就是把区间查询的操作倒了过来
操作 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
在二进制的最后一个1的位置加1 | ||||
转化成十进制 | 5 | 6 | 8 | 16 |
管辖的范围 |
这里就不再阐述了
总结:查询就是在最后一个1的位置减1,修改就是在最后一个1的位置加1#
那怎么得到二进制的最后一个1在哪里
操作#
inline int lowbit(int x) { return x & -x; }
这段代码是什么含义呢?
先谈谈二进制中如何表示负数:
一个正数的相反数用二进制表示就是这个数按位取反再+1
比如说的负数用二进制表示就是+1,也就是
&=
那为什么能求二进制最末位的1在哪?
我们要求的这个数末尾连续的0取反之后会全部变成1(就是二进制下这个数的相反数减一之后末尾的1与这个数末尾的0的数量相同)
在负数中把减去的1加上之后,末尾的1会全部变成0,而最末尾的0会变成1(加法)
放上核心代码:
int n, f[100003];
inline int lowbit(int x) { return x & -x; }
inline int query(int x)//查询
{
int ans = 0;
while (x)
ans += f[x], x -= lowbit(x);
return ans;
}
inline void add(int x, int val)//修改
{
while (x <= n)
f[x] += val, x += lowbit(x);
}
P.S.求区间和就是求的值
问题P3368区间修改和单点查询#
如果上面宁已经看懂了,接下来的内容就很简单了
区间修改,其实用到了差分的思想
之前的是指~的和(及前缀和),这里储存的是差分数组
还是拿百度的图举例子
假如此时我们要把区间都加上值应该如何操作呢?
利用差分的思想,在1的位置上加上val,在6+1的位置上减去val
这样查询时,就正好加上了值,查询到大于6的时候,先加了,又减去了正好抵消
然后给出代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,opt,l,r,k,pre,now,f[500005];
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline ll lowbit(ll x){return x&-x;}
inline ll query(ll x){ll ans=0;while(x) ans+=f[x],x-=lowbit(x);return ans;}
inline void add(ll x,ll val){while(x<=n) f[x]+=val,x+=lowbit(x);}
int main(){
n=read(),m=read();
for(ll i=1;i<=n;++i){now=read();add(i,now-pre);pre=now;}
while(m--){
opt=read();
if(opt==1) {l=read(),r=read(),k=read();add(l,k),add(r+1,-k);}
else k=read(),printf("%lld\n",query(k));
}
return 0;
}
欢迎提出建议
完结撒花
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现