树状数组学习笔记
目录
-
原理(结构)
-
建树
-
应用
-
单点修改,区间求和
-
区间修改,单点求值
-
区间修改,区间求和
-
单点修改,区间求最值
-
求逆序对个数
-
二维树状数组
-
trick:树状数组上倍增
-
权值树状数组
-
正文
1. 原理
引用日报图片。
设黑色框内数组为
可以推得
问题转变为求出二进制中从最低位到高位连续零的长度。
此时,我们引入 lowbit(x)=x&(-x)
。
其实可以发现,树状数组其实就是对原数组做一个特殊的前缀和,使得其可以
2. 建树
一般使用
但有两种方法可以达到
- 根据定义,
表示区间 的和,可以直接前缀和。
for(int i=1;i<=n;i++)a[i]+=a[i-1],tr[i]=a[i]-a[i-(i&-i)];
-
算贡献。这个结合代码理解比较好。
for(int i=1;i<=n;i++) { scanf("%d",&a[i]); for(int j=1;j<lowbit(i);j*=2){ a[i]+=a[i-j]; } } for(int i=1;i<=n;i++){ scanf("%lld",&x); a[i]+=x; if(i+lowbit(i)<=n)a[i+lowbit(i)]+=a[i];//注意这里的条件 } 两个代码等价,所以复杂度为
。显然也可以直接算一下第一个的大小,发现是 的。3. 应用
1)单点修改,区间求和
最基本的应用。
ll qry(int x) { ll ans=0; for(; x; x-=lowbit(x)) ans+=tr[x]; return ans; } void upd(int x,ll y) { for(; x<=n; x+=lowbit(x)) tr[x]+=y; }
2)区间修改,单点查询
显然可以记 b
数组为原数组的差分数组,然后在区间
3)区间修改,区间查询
记原数组
同时维护
int a[maxn],b[maxn],ib[maxn],n,m; ll query(int x) { ll ans=0; int z=x; for(; x; x-=lowbit(x)) ans+=z*b[x]-ib[x]; return ans; } void upd(int x,ll y) { int z=x; for(; x<=n; x+=lowbit(x)) b[x]+=y,ib[x]+=y*(z-1); }
4)单点修改,区间最值
有点抽象,直接背板。
ll a[maxn],tr[maxn]; const ll INF=1e10; int n,m; void upd(int x,ll y) { for(; x<=n; x+=lowbit(x)) tr[x]=max(tr[x],y); } ll query(int l,int r) { ll res=-INF; for(; r>=l&&r-l+1>=lowbit(r); r-=lowbit(r)) res=max(res,tr[r]); while(r>=l) { res=max(res,a[r]); if(r-l+1<lowbit(r)) r--; else res=max(res,tr[r]),r-=lowbit(r); } return res; }
5)求逆序对/顺序对
给出逆序对代码。
struct element { ll val; int id; bool operator < (element x) const { return x.val<val; } } a[maxn]; int n,b[maxn],tr[maxn]; ll query(int x) { ll ans=0; for(; x; x-=lowbit(x)) ans+=tr[x]; return ans; } void upd(int x,int o) { for(; x<=n; x+=lowbit(x)) tr[x]+=o; } void solve() { cin>>n; for(int i=1; i<=n; i++) cin>>a[i].val,a[i].id=i; stable_sort(a+1,a+n+1); for(int i=1; i<=n; i++) b[a[i].id]=i; ll ans=0; for(int i=n; i; i--) { upd(b[i],1); ans+=query(b[i]-1); } cout<<ans<<'\n'; }
6)二维树状数组
Useless algorithm!!!!!!!!!!!!!!!!111
7)树状数组上倍增
一个很神的 trick,可以在
开始二分,设初始位置为
考虑依次跳
发现往后跳的段都是诸如
例题:P6619 [省选联考 2020 A/B 卷] 冰火战士。
Solve
发现因为冰、火战士的特殊性,对冰、火战士的温度从小到大排序后,可用的冰战士是所有冰战士的一段前缀,可用火战士是所有火战士的一段后缀。
考虑记
则答案在单峰函数的峰顶左右两侧。考虑二分找到最大的
显然答案不是
8)权值树状数组
使用条件:值域小 或 不强制在线(不强制在线时可以离散化)
假设维护一个桶
-
插入一个数。即桶内这个位置加1。
-
删除一个数。即桶内这个位置减1。
-
求
的排名。即求 。 -
求排名为
的数。记
表示 的排名。则 。我们需要找到最大的
使得 。可以用到上面树状数组上倍增的技巧,时间复杂度仍然为 。int kth(int k,int r) {//r为开始二分的位置 int t=0; //w 为值域 for(int i=__lg(w),x,y; i>=0; i--) { if((x=r+(1<<i))>w) continue; if((y=(t+tr[x])<k) r=x,t=y; } return r+1; } - 求
的前驱,即求 。 - 求
的后继,即求 。
- 求
给出参考实现 by diqiuyi:
#include <bits/stdc++.h> using namespace std; const int maxn=100010; int n,a[maxn],t[maxn],cnt,tot; pair<int,int> p[maxn]; #define lowbit(x) (x&(-x)) inline void Add(int x,int val) { for(; x<=cnt; x+=lowbit(x)) t[x]+=val; } inline int query(int x) { int res=0; for(; x; x-=lowbit(x)) res+=t[x]; return res; } inline int rnk(int x) { int res=0,sum=0; for(int i=17; ~i; i--) if(res+(1<<i)<=cnt&&sum+t[res+(1<<i)]<x) sum+=t[res+(1<<i)],res+=(1<<i); return res+1; } int main() { ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n; for(int i=1; i<=n; i++) { cin>>p[i].first>>p[i].second; if(p[i].first^4) a[++tot]=p[i].second; } sort(a+1,a+tot+1); cnt=unique(a+1,a+tot+1)-a-1; for(int i=1; i<=n; i++) if(p[i].first^4) p[i].second=lower_bound(a+1,a+cnt+1,p[i].second)-a; for(int i=1; i<=n; i++) { if(p[i].first==1) Add(p[i].second,1); else if(p[i].first==2) Add(p[i].second,-1); else if(p[i].first==3) cout<<query(p[i].second-1)+1<<'\n'; else if(p[i].first==4) cout<<a[rnk(p[i].second)]<<'\n'; else if(p[i].first==5) cout<<a[rnk(query(p[i].second-1))]<<'\n'; else cout<<a[rnk(query(p[i].second)+1)]<<'\n'; } return 0; }
参考
本文作者:lgh_2009
本文链接:https://www.cnblogs.com/lgh-blog/p/18042934
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步