1. 程式人生 > 實用技巧 >樹狀陣列從入門到棄療

樹狀陣列從入門到棄療

目錄

樹狀陣列是一類儲存字尾和,更新字尾和,通過lowbit來限定字尾和的長度,利用二進位制使得查詢、更新的時間複雜度都在\(O(logn)\)的資料結構,碼量十分小,常數優秀

注意:以下下程式碼部分未經過壓力測試,不保證完全正確

單點修改+區間查詢

樹狀陣列 1

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e6+2020;
int c[N],a[N],n,q;
int lowbit(int x) {
	return x&-x;
}
int sum(int r) {
	int ret=0;
	while(r>0) ret+=c[r],r-=lowbit(r);
	return ret;
}
void add(int x,int val) {
	while(x<=n) c[x]+=val,x+=lowbit(x);
}
signed main() {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	cin>>n>>q;
	for(int i=1; i<=n; ++i) 
		scanf("%lld",&a[i]),add(i,a[i]);
	for(int i=1; i<=q; ++i) {
		int opt,val,x;
		scanf("%lld %lld %lld",&opt,&x,&val);
		if(opt==1) a[x]+=val,add(x,val);
        else if(opt==2) cout<<sum(val)-sum(x-1)<<'\n';
	}
	return 0;
}

區間修改,單點查詢

樹狀陣列 2

利用差分陣列還原原陣列的方法即可實現單點查詢

顯然差分陣列可以快速修改區間

我是\(SB\)

/*
@ author:pyyyyyy/guhl37
-----思路------

-----debug-------

*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e6+2020;
int delta[N],n,q,a[N];
int lowbit(int x) {
	return x&(-x);
}
void add(int x,int val) {
	while(x<=n) delta[x]+=val,x+=lowbit(x);
}
int sum(int r) {
	int ret=0;
	while(r) ret+=delta[r],r-=lowbit(r);
	return ret;
}
signed main() {
	cin>>n>>q;
	for(int i=1; i<=n; ++i)
		scanf("%lld",&a[i]);
	while(q--) {
		int opt,l,r,x;
		scanf("%lld",&opt);
		if(opt==1) scanf("%lld %lld %lld",&l,&r,&x),add(l,x),add(r+1,-x);
		else if(opt==2) scanf("%lld",&x),cout<<a[x]+sum(x)<<'\n';
	}
	return 0;
}

區間修改+區間查詢

樹狀陣列 3

對一個差分陣列做一次字首和可以得到每個位置的值再對每個位置累加一下就是一個區間的值

對於差分陣列\(delta\)

差分陣列的字首和為\(val_i=\sum\limits_{j=1}^idelta_j\)

對於區間\([l,r]\)

\(s_{l,r}=\sum\limits_{i=1}^rval_i-\sum\limits_{i=1}^{l-1}val_i\)(字首和相減的形式)

可以發現,一個區間的值實際上就是差分陣列字首和的字首和做減法

我們可以用樹狀陣列維護差分陣列字首和的字首和

\(s_p=\sum\limits_{i=1}^p\sum\limits_{j=1}^idelta_j\)

\(s_p=\sum\limits_{i=1}^p\left(p-i+1\right)c_i=\left(p+1\right)\sum\limits_{i=1}^pc_i-\sum\limits_{i=1}^pi *c_i\)

顯然這些東西都可用樹狀陣列維護一下

/*
@ author:pyyyyyy/guhl37
-----思路------

-----debug-------
add裡面為什麼條件是<=N?
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2000005;
int n,q;
int lowbit(int x) {
	return x&-x;
}
void add(int *arr,int x,int val) {
	while(x<=N) arr[x]+=val,x+=lowbit(x);
	//這是為什麼是x<=N? 
}
int sum(int *arr,int x) {
	int ret=0;
	while(x) ret+=arr[x],x-=lowbit(x);
	return ret;
}
int a[N],d[N],id[N];
int ans(int k) {
	return k*sum(d,k)-sum(id,k);
}
signed main() {
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	cin>>n>>q;
	for(int i=1; i<=n; ++i) {
		scanf("%lld",&a[i]);
		add(d,i,a[i]-a[i-1]);
		add(id,i,(i-1)*(a[i]-a[i-1]));
	}
	while(q--) {
		int opt,l,r,x;
		cin>>opt;
		if(opt==1) {
			scanf("%lld %lld %lld",&l,&r,&x);
			add(d,l,x),add(d,r+1,-x);
			add(id,l,(l-1)*x),add(id,r+1,-r*x);
		} else if(opt==2) {
			scanf("%lld %lld",&l,&r);
			cout<<ans(r)-ans(l-1)<<'\n';
		}
	}
	return 0;
}

上面有個不太懂的地方,懇請大佬解答

區間最值

沒想到樹狀陣列能幹這個 ,其實常數也蠻大的了,沒什麼意義,還不如寫線段樹

void build(int n){
	for(int i=1;i<=n;++i)
	{
		c[i]=a[i];int t=lowbit(i);
		for(int j=1;j<t;j*=2) c[i]=max(c[i],c[i-j]);
	}
}
void add(int pos,int x)
{
	a[pos]=x;
	while(pos<=n){
		c[pos]=a[pos];int t=lowbit(i);
		for(int j=1;j<t;j*=2) c[i]=max(c[i],c[i-j]);
		pos+=lowbit(pos);
	}
}
int query(int l,int r)
{
	int ans=a[r];
	while(1)
	{
		ans=max(ans,num[r]);
		if(r==1) break;
		r--;
		while(r-l>=lowbit(r)) ans=max(ans,c[r]),r-=lowbit(r);
	}
	return ans;
}

二維樹狀陣列--單點修改,區間查詢

二維樹狀陣列 1

二維樹狀陣列--區間修改,單點查詢

二維樹狀陣列 2

二維樹狀陣列--區間修改,區間查詢

二維樹狀陣列 3

參考資料

樹狀陣列簡單易懂的詳解

可以代替線段樹的樹狀陣列?——樹狀陣列進階

樹狀陣列的區間修改,區間查詢

樹狀陣列 3 :區間修改,區間查詢