//悲观者永远正确,乐观者永远前行。|

ccrui

园龄:2年2个月粉丝:2关注:4

2023-01-18 20:49阅读: 40评论: 0推荐: 0

树状数组学习笔记


树状数组简介

树状数组或二叉索引树(英语: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

  • 4=(100)2 -> lowbit(100)=(100)2=4lowbit(4)=4
  • 6=(110)2 -> lowbit(110)=(10)2=2lowbit(6)=2
  • 7=(111)2 -> lowbit(111)=(1)2=1lowbit(7)=1

树状数组

树状数组是一个可以快速处理前缀信息的数据结构

树状数组一般使用数组进行存储,数组中 x 号节点,存储的是区间 [xlowbit(x)+1,x] 的信息和,利用这
一点可以在线性时间内建出树状数组),也可以实现全局的第 k 小查询,其中 tree[x] 表示 i=xlownit(x)+1xai

image

树状数组特性:

  • 每个内部节点 tree[x] 保存以它为根的所有叶子节点的和
  • 每个内部节点 tree[x] 的子节点数等于 lowbit(x) 的位数
  • 每个内部节点 tree[x] 的父节点是 tree[x+lowbit(x)]
  • 树的深度为 logn

树状数组实现

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.单点修改

修改函数与上面建树函数相同
我们如果更改点 1 ,需要更改的节点如图红圈部分:
image

1

int add(int x,int y){
while(x<=n){
t[x]+=y;//此节点同所有祖先节点都增加
x+=x&-x;//与x=x+lowbit(x)等价
}
}

调用入口:add(x,a);

3.查询

我们如果查询区间 [4,7] ,先求出区间 [1,7] (红色线段表示)和区间 [1,3] (蓝色线段表示)再相减(绿色线段表示),需要计算的节点如图红圈( 区间 [1,7] 需要计算的节点)及蓝圈( 区间 [1,3] 需要计算的节点)部分:
image

[4,7]

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);

单点查询要借助区间查询,如果信息支持区间差的话,可以用 [1,y] 的值减去 [1,y1] 的值

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);

如果修改单点则把 l,r 都赋值为 x

单点查询

调用入口变为:ask(x)

区间查询

N

树状数组到这就结束了,练习一下吧!

例题1

luogu P3374 【模板】树状数组 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

luogu P3368 【模板】树状数组 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;
}

二维树状数组

先推式子

i=1xj=1xx1=1ix2=1jax1,y1=i=1xj=1xai,j×(x+1i)×(y+1j)=(x+1)×(y+1)×i=1xj=1yai,j(y+1)×i=1xj=1yai,j×i(x+1)×i=1xj=1yai,j×j+i=1xj=1yai,j×i×j

然后我们开 4 个树状数组分别记录

i=1xj=1yai,j(1)i=1xj=1yai,j×i(2)i=1xj=1yai,j×j(3)i=1xj=1yai,j×i×j(4)

再利用二维差分实现区间修改,二维前缀和实现区间查询即可

P4514 上帝造题的七分钟

点击查看代码
#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;
}

求逆序对

二元逆序对

P1908 逆序对

排序后记录每个点原来的位置,对于每个数则加入树状数组,并求出它之前的数的数量 h,它的值即为 ih

点击查看代码
#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;
}

三元逆序对

Enemy is weak

我们枚举中间的节点,再把前后 大于且原 i 值小于它 和 小于且原 i 值大于它 的节点数相乘即可。

点击查看代码
#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 中国大陆许可协议进行许可。

posted @   ccrui  阅读(40)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示