树状数组与线段树
一.树状数组
\(\color{black}1.树状数组的定义\)
\(2.\) 树状数组的结构及其性质
树状数组的结构

引入\(lowbit(x)\)
\(lowbit(x)\) 的作用是寻找\(x\)二进制下的第一个"\(1\)"位(如\(14=8+4+2={(1110)}_2\)它的\(lowbit\)值为\((10)_2\)即\(2\),而其求解可以用\(x\)&\(-x\)做,即
int lowbit(int x){return x&-x;}
树状数组的性质
\((1)\) 对于一个节点\(u\) , \(u+lowbit(u)\) 就是其父节点
\((2)\) 对于一个节点\(u\) ,\(u-lowbit(u)\)为其前一个与其无交集的节点
\((3)\) 对于原数组中的节点\(x\) 在树状数组中包含他的节点是\(x\)及其祖先节点
\((4)\) 从根节点到任意一个叶子节点,需要\(O(logN)\)次遍历
树状数组的操作
\((1)\) \(update:\)假设我们对于\(x\)节点加上\(k\),那么根据性质\(3\),我们需要对\(x\)及其祖先结点\(+k\)
void update(int p,int x){//在p这个位置加x
for(;p<=n;p+=lowbit(x)) t[p]+=x;//遍历p及其祖先,性质1
}
\((2)\) \(query\):我们求从\(1\rightarrow x\)的前缀和,根据性质\(2\),有
int query(int x){//1->x前缀和
int res=0;
for(;x;x-=lowbit(x)) res+=t[x];//从1->x遍历
return res;
}
根据以上操作,可以维护简单的树状数组
#include<bits/stdc++.h>
#define N 1000000
using namespace std;
int tree[N];int n,m;
int lowbit(int x){return x&(-x);}
void update(int p,int x){for(;p<=n;p+=lowbit(p)) tree[p]+=x;}
int ask(int end){
int res=0;
for(;end;end-=lowbit(end)) res+=tree[end];
return res;
}
int query(int l,int r){return ask(r)-ask(l-1);}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int x;cin>>x;
tree[i]+=x;
if(i+lowbit(i)<=n) tree[i+lowbit(i)]+=tree[i];
}
while(m--){
int op,x,y;
cin>>op>>x>>y;
if(op==1) update(x,y);
else cout<<query(x,y)<<"\n";
}
return 0;
}
树状数组的复杂度
由于树状数组的结构,其一共有\(logN\)层,故一次\(update\)或\(query\)均为\(logN\)复杂度,总复杂度\(O(qlogN)\)
树状数组的应用
\(1.\)单点修改,区间查询
\(2.\)单点修改,单点查询
\(3.\)区间修改,区间查询(借助差分数组)
\(4.\)\(\color{black}离线二维数点\)
该题思路:将点 \(a_i\)看做平面直角坐标系上的点\((i,a_i)\)

即是求矩形内的点数,矩形左下坐标\((l,0)\)右上坐标\((r,x)\) 考虑用一条线从左向右扫
#include<bits/stdc++.h>
const int N=2e6+7;
using namespace std;
int n,m,a[N],t[N],ans[N];
int lowbit(int x){return x&(-x);}
void add(int p,int x){for(int i=p;i<N;i+=lowbit(i)) t[i]+=x; }
int query(int x){
int res=0;
for(int i=x;i>0;i-=lowbit(i)) res+=t[i];
return res;
}
struct node{
int x,id,op;
};vector<node> q[N];
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=m;i++){
int l,r,x;cin>>l>>r>>x;
q[l-1].push_back(node{x,i,-1});//离线
q[r].push_back(node{x,i,1});
}
for(int i=1;i<=n;i++){//扫描
add(a[i],1);
for(auto y:q[i]){
int x=y.x,id=y.id,op=y.op;
ans[id]+=op*query(x);
}
}
for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
return 0;
}
二,线段树
\((1).\)\(\color{black}{线段树的定义}\)

浙公网安备 33010602011771号