树状数组学习笔记
树状数组简介
树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为Fenwick树,最早由Peter M. Fenwick于1994年以A New Data Structure for Cumulative Frequency Tables为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和。
——百度百科
前置知识:lowbit
lowbit是指一个数最低位的
- -> 即
- -> 即
- -> 即
树状数组
树状数组是一个可以快速处理前缀信息的数据结构
树状数组一般使用数组进行存储,数组中 号节点,存储的是区间 的信息和,利用这
一点可以在线性时间内建出树状数组),也可以实现全局的第 小查询,其中 表示
树状数组特性:
- 每个内部节点 保存以它为根的所有叶子节点的和
- 每个内部节点 的子节点数等于 的位数
- 每个内部节点 的父节点是
- 树的深度为
树状数组实现
1.建树
直接用加点函数循环
int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点赋值,所有祖先节点都增加(与前缀和一样) x+=x&-x;//与x=x+lowbit(x)等价 } }
调用入口:
for(int i=1;i<=n;i++){//点得一个一个加 cin>>a[i]; add(i,a[i]); }
2.单点修改
修改函数与上面建树函数相同
我们如果更改点 ,需要更改的节点如图红圈部分:
int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点同所有祖先节点都增加 x+=x&-x;//与x=x+lowbit(x)等价 } }
调用入口:add(x,a);
3.查询
我们如果查询区间 ,先求出区间 (红色线段表示)和区间 (蓝色线段表示)再相减(绿色线段表示),需要计算的节点如图红圈( 区间 需要计算的节点)及蓝圈( 区间 需要计算的节点)部分:
int ask(int x){ int val=0; while(x>0){ val+=t[x];//求出1-x的值 x-=x&-x;//与x=x-lowbit(x)等价 } return val;//返回答案 }
调用入口:ask(r)-ask(l-1);
单点查询要借助区间查询,如果信息支持区间差的话,可以用 的值减去 的值
int ask(int x){ int val=0; while(x>0){ val+=t[x];//求出1-x的值 x-=x&-x;//与x=x-lowbit(x)等价 } return val;//返回答案 }
调用入口:ask(x)-ask(x-1);
4.区间修改
区间修改得更改很多地方
建树
int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点赋值,所有祖先节点都增加(与前缀和一样) x+=x&-x;//与x=x+lowbit(x)等价 } }
调用入口:
for(int i=1;i<=n;i++){//点得一个一个加 cin>>a[i]; add(i,a[i]-a[i-1]); }
修改
int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点同所有祖先节点都增加 x+=x&-x;//与x=x+lowbit(x)等价 } }
调用入口:
add(l,k); add(r+1,-k);
如果修改单点则把 都赋值为
单点查询
调用入口变为:ask(x)
区间查询
树状数组到这就结束了,练习一下吧!
例题1
这题可以用用树状数组单点修改,区间查询来做
点击查看题目
#include<bits/stdc++.h> using namespace std; int t[500001],a[500001],n,m; int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点同所有祖先节点都增加 x+=x&-x;//与x=x+lowbit(x)等价 } } int ask(int x){ int val=0; while(x>0){ val+=t[x];//求出1-x的值 x-=x&-x;//与x=x-lowbit(x)等价 } return val;//返回答案 } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){//点得一个一个加 cin>>a[i]; add(i,a[i]); } for(int i=1;i<=m;i++){ int flag,x,y; cin>>flag>>x>>y; if(flag==1){ add(x,y);//增加y }else{ cout<<ask(y)-ask(x-1)<<endl;//输出区间[x,y]的值 } } return 0; }
例题2
这题可以用用树状数组区间修改,单点查询来做
点击查看题目
#include<bits/stdc++.h> using namespace std; int t[500001],a[500001],n,m; int add(int x,int y){ while(x<=n){ t[x]+=y;//此节点同所有祖先节点都增加 x+=x&-x;//与x=x+lowbit(x)等价 } } int ask(int x){ int val=0; while(x>0){ val+=t[x];//求出1-x的值 x-=x&-x;//与x=x-lowbit(x)等价 } return val;//返回答案 } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){//点得一个一个加 cin>>a[i]; add(i,a[i]-a[i-1]); } for(int i=1;i<=m;i++){ int flag,x,y,k; cin>>flag>>x; if(flag==1){ cin>>y>>k; add(x,k); add(y+1,-k);//区间增加y }else{ cout<<ask(x)<<endl;//输出点x的值 } } return 0; }
二维树状数组
先推式子
然后我们开 个树状数组分别记录
再利用二维差分实现区间修改,二维前缀和实现区间查询即可
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; int n,m; struct twoBIT{ int t[2500][2500][4]; int add(int x,int y,int z){ int nx=x; while(nx<=n){ int ny=y; while(ny<=m){ t[nx][ny][0]+=z;//1式sigma t[nx][ny][1]+=(x-1)*z;//2式sigma t[nx][ny][2]+=(y-1)*z;//3式sigma t[nx][ny][3]+=(x-1)*(y-1)*z;//4式sigma ny+=ny&-ny; } nx+=nx&-nx; } } int ask(int x,int y){ int val=0; int nx=x; while(nx>0){ int ny=y; while(ny>0){ val+=t[nx][ny][0]*x*y;//1式*系数 val-=t[nx][ny][1]*y;//2式*系数 val-=t[nx][ny][2]*x;//3式*系数 val+=t[nx][ny][3];//4式*系数1 ny-=ny&-ny; } nx-=nx&-nx; } return val; } }t1; signed main(){ char c; cin>>c>>n>>m; while(cin>>c){ if(c=='L'){ int l1,r1,l2,r2,x; cin>>l1>>r1>>l2>>r2>>x; t1.add(l1,r1,x); t1.add(l1,r2+1,-x); t1.add(l2+1,r1,-x); t1.add(l2+1,r2+1,x);//二维差分 }else{ int l,r,l1,r1; cin>>l>>r>>l1>>r1; int ans=0; ans=t1.ask(l1,r1); ans-=t1.ask(l-1,r1); ans-=t1.ask(l1,r-1); ans+=t1.ask(l-1,r-1);//二维前缀和 cout<<ans<<endl; } } return 0; }
求逆序对
二元逆序对
排序后记录每个点原来的位置,对于每个数则加入树状数组,并求出它之前的数的数量 ,它的值即为 。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; int n; struct node{ int a,i; bool operator <(const node &aa)const{ if(a==aa.a)return i<aa.i; return a<aa.a; } }a[1000001]; int rs[500010]; struct BIT{ int t[500100]; int add(int x,int y){ while(x<=n){ t[x]+=y; x+=x&-x; } } int ask(int x){ int val=0; while(x>0){ val+=t[x]; x-=x&-x; } return val; } }t1; signed main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i].a; a[i].i=i; } sort(a+1,a+n+1);//排序 for(int i=1;i<=n;i++){ rs[a[i].i]=i;//记录每个值排序所在位置 } int ans=0; for(int i=1;i<=n;i++){ t1.add(rs[i],1);//加入树状数组 ans+=i-t1.ask(rs[i]);//求有多少个在它之前,再反推 } cout<<ans<<endl; return 0; }
三元逆序对
我们枚举中间的节点,再把前后 大于且原 值小于它 和 小于且原 值大于它 的节点数相乘即可。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; int n; struct node{ int a,i; bool operator <(const node &aa)const{ if(a==aa.a)return i<aa.i; return a<aa.a; } }a[1000001]; bool cmp(node a,node aa){ if(a.a==aa.a)return a.i<aa.i; return a.a>aa.a; } int rs[1000010],rs2[1000010]; struct BIT{ int t[1000100]; int add(int x,int y){ while(x<=n){ t[x]+=y; x+=x&-x; } } int ask(int x){ int val=0; while(x>0){ val+=t[x]; x-=x&-x; } return val; } }t1,t2; int l1[1000001],r1[1000001]; signed main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i].a; a[i].i=i; } sort(a+1,a+n+1); for(int i=1;i<=n;i++){ if(a[i].a!=a[i-1].a||i==1)rs[a[i].i]=i; else rs[a[i].i]=rs[a[i-1].i]; } int ans=0; for(int i=n;i>0;i--){ l1[i]=t1.ask(rs[i]-1); t1.add(rs[i],1); } for(int i=1;i<=n;i++){ r1[i]=t2.ask(n-rs[i]); t2.add(n-rs[i]+1,1); } for(int i=1;i<=n;i++){ ans+=l1[i]*r1[i]; } cout<<ans<<endl; return 0; }
权值树状数组
核心代码:
点击查看代码
struct BIT{ int t[1000100]; int add(int x,int y){ while(x<=n){ t[x]+=y; x+=x&-x; } } int kth(int k){ int r=0,st=0,x,y; for(int i=log2(n);i>=0;i--){ x=r+(1<<i),y=st+t[x]; if(x>n)continue; if(y<k)r=x,st=y; } return r+1; } };
本文作者:ccrui
本文链接:https://www.cnblogs.com/ccr-note/p/bitree.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步