树状数组与线段树

一.树状数组

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

树状数组的结构

kkkk

引入\(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)\)
111111
即是求矩形内的点数,矩形左下坐标\((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}{线段树的定义}\)

posted @ 2026-03-28 19:52  Underthetwilight  阅读(5)  评论(0)    收藏  举报