【模板】一维树状数组
ACM模板
目錄
- 聊聊前綴和
- 什么是樹狀數(shù)組?
- 樹狀數(shù)組相關(guān)操作
- 局限性
- 差分在樹狀數(shù)組中的應(yīng)用
- 區(qū)間更新、單點(diǎn)查詢
- 區(qū)間更新、區(qū)間查詢
- 樹狀數(shù)組應(yīng)用
聊聊前綴和
比如數(shù)組
int a[7]={1,2,3,4,5,6,7}如果需詢問數(shù)組從第l個(gè)數(shù)到第r個(gè)數(shù)的和暴力做法時(shí)間復(fù)雜度為O(n)O(n)O(n)
不過我們可以預(yù)處理一個(gè)前綴和數(shù)組
int b[7]={1,3,6,10,15,21,28}比如要詢問[l,r]區(qū)間的和我們可以這樣做b[r]-b[l-1]這也時(shí)間復(fù)雜度為O(1)O(1)O(1)
但是問題來了,如果我們要既要修改數(shù)組中元素的值,有要進(jìn)行上述區(qū)間查詢操作呢?
我們發(fā)現(xiàn)每次我們修改原數(shù)組中元素的值時(shí)間復(fù)雜度為O(1)O(1)O(1)但是如果修改前綴和數(shù)組中元素的值時(shí)間復(fù)雜度將會(huì)退化到O(n)O(n)O(n)
總結(jié)一下:
| 原數(shù)組 | O(1) | O(n) |
| 前綴和數(shù)組 | O(n) | O(1) |
我們可以發(fā)現(xiàn)如果需要單點(diǎn)更新和區(qū)間查詢兩種操作時(shí)間復(fù)雜度都是O(n)O(n)O(n)
什么是樹狀數(shù)組?
樹狀數(shù)組是一種便于進(jìn)行單點(diǎn)更新和區(qū)間查詢的數(shù)據(jù)結(jié)構(gòu)
樹狀數(shù)組相關(guān)操作
單點(diǎn)更新
我們對(duì)數(shù)組位置為x的元素加上c
局限性
我們很容易發(fā)現(xiàn)上述樹狀數(shù)組只適用于單點(diǎn)更新和區(qū)間查詢,但是如果是區(qū)間修改和單點(diǎn)查詢好像力不從心
差分在樹狀數(shù)組中的應(yīng)用
告訴你個(gè)好消息如果有差分的介入,那么樹狀數(shù)組可以進(jìn)行區(qū)間更新和單點(diǎn)查詢當(dāng)然也可以進(jìn)行更厲害的區(qū)間更新和區(qū)間查詢。
區(qū)間更新、單點(diǎn)查詢
我們把tree[]tree[]tree[]數(shù)組構(gòu)造成一個(gè)差分?jǐn)?shù)組還是看題吧
題目
給定長度為N的數(shù)列A,然后輸入M行操作指令。
第一類指令形如“C l r d”,表示把數(shù)列中第l~r個(gè)數(shù)都加d。
第二類指令形如“Q X”,表示詢問數(shù)列中第x個(gè)數(shù)的值。
對(duì)于每個(gè)詢問,輸出一個(gè)整數(shù)表示答案。
輸入格式
第一行包含兩個(gè)整數(shù)N和M。
第二行包含N個(gè)整數(shù)A[i]。
接下來M行表示M條指令,每條指令的格式如題目描述所示。
輸出格式
對(duì)于每個(gè)詢問,輸出一個(gè)整數(shù)表示答案。
每個(gè)答案占一行。
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; const int N=100010; ll tree[N]; int n,m; int lowbit(int x) {return x&-x; } void add(int x,int c) {for(;x<=n;x+=lowbit(x)) tree[x]+=c; } ll sum(int x) {int res=0;for(;x;x-=lowbit(x)) res+=tree[x];return res; } int main() {cin>>n>>m;for(int i=1;i<=n;i++){int a;cin>>a;add(i,a);add(i+1,-a);}while(m--){char t;cin>>t;if(t=='Q'){int x;cin>>x;cout<<sum(x)<<endl;}else{int a,b,c;cin>>a>>b>>c;add(a,c);add(b+1,-c);}}return 0; }我們可以發(fā)現(xiàn)對(duì)于上述代碼即在建樹的過程中建成差分樹的形式即可
區(qū)間更新、區(qū)間查詢
原數(shù)組a[],對(duì)于區(qū)間更新我們可以維護(hù)一個(gè)差分?jǐn)?shù)組b[]
如果我們維護(hù)數(shù)組a的前綴和我們可以發(fā)現(xiàn)有下面等式:
∑i=1xai=∑i=1x∑j=1ibi=∑i=1x(x?i+1)bi\sum_{i=1}^x a_i=\sum_{i=1}^x\sum_{j=1}^i b_i=\sum_{i=1}^x(x-i+1)b_i i=1∑x?ai?=i=1∑x?j=1∑i?bi?=i=1∑x?(x?i+1)bi?
變換一下:
∑i=1xai=(x+1)∑i=1xbi?∑i=1xbi×i\sum_{i=1}^x a_i=(x+1)\sum_{i=1}^x b_i-\sum_{i=1}^x b_i×i i=1∑x?ai?=(x+1)i=1∑x?bi??i=1∑x?bi?×i
于是我們可以維護(hù)兩個(gè)差分樹狀數(shù)組tree1[]tree1[]tree1[]維護(hù)$ bi、、、 tree2[]維護(hù)維護(hù)維護(hù)i*bi$
給定一個(gè)長度為N的數(shù)列A,以及M條指令,每條指令可能是以下兩種之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示詢問 數(shù)列中第 l~r 個(gè)數(shù)的和。
對(duì)于每個(gè)詢問,輸出一個(gè)整數(shù)表示答案。
輸入格式
第一行兩個(gè)整數(shù)N,M。
第二行N個(gè)整數(shù)A[i]。
接下來M行表示M條指令,每條指令的格式如題目描述所示。
輸出格式
對(duì)于每個(gè)詢問,輸出一個(gè)整數(shù)表示答案。
每個(gè)答案占一行。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring>using namespace std;typedef long long ll;const int N=100010;int n,m; ll tree1[N],tree2[N]; //維護(hù)b[i] 維護(hù)i*b[i] int lowbit(int x) {return x&-x; }void add(ll tree[],int x,ll c) {for(;x<=n;x+=lowbit(x)) tree[x]+=c; }ll sum(ll tree[],int x) {ll res=0;for(;x;x-=lowbit(x)) res+=tree[x];return res; } ll prefix_sum(int x) {return (x+1)*sum(tree1,x)-sum(tree2,x); }int main() {scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){int a;scanf("%d",&a);add(tree1,i,a);add(tree1,i+1,-a);add(tree2,i,1ll*i*a);add(tree2,i+1,-1ll*(i+1)*a);}while(m--){char t;int l,r;cin>>t>>l>>r;if(t=='Q'){scanf("%d%d",&l,&r);cout<<prefix_sum(r)-prefix_sum(l-1)<<endl;}else{int d;scanf("%d",&d);add(tree1,l,d),add(tree1,r+1,-d);add(tree2,l,l*d),add(tree2, r+1,-1ll*(r+1)*d);}}return 0; }樹狀數(shù)組應(yīng)用
區(qū)間更新和區(qū)間查詢
逆序?qū)?/strong>:首先有一種求逆序?qū)Φ姆椒?#xff1a;開一個(gè)數(shù)組cnt[n]?然后遍歷數(shù)組中的每一個(gè)數(shù),在cnt[]數(shù)組中留下標(biāo)記:比如數(shù)組中第i個(gè)元素為x,那就cnt[x]=1,所謂逆序即:我比你先出現(xiàn)而且比你大,對(duì)于上述例子所謂比你大即在[x+1,n]這個(gè)區(qū)間中的值,所謂比你先出現(xiàn)即在第i-1次遍歷時(shí)被標(biāo)記過。轉(zhuǎn)化一下相當(dāng)于求cntcntcnt數(shù)組中[x+1,n]區(qū)間內(nèi)的和
第k小的數(shù)
總結(jié)
以上是生活随笔為你收集整理的【模板】一维树状数组的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国有哪些第一名
- 下一篇: OpenJudge1043 树上游戏(换