【模板】一维树状数组
ACM模板
目錄
- 聊聊前綴和
- 什么是樹狀數組?
- 樹狀數組相關操作
- 局限性
- 差分在樹狀數組中的應用
- 區間更新、單點查詢
- 區間更新、區間查詢
- 樹狀數組應用
聊聊前綴和
比如數組
int a[7]={1,2,3,4,5,6,7}如果需詢問數組從第l個數到第r個數的和暴力做法時間復雜度為O(n)O(n)O(n)
不過我們可以預處理一個前綴和數組
int b[7]={1,3,6,10,15,21,28}比如要詢問[l,r]區間的和我們可以這樣做b[r]-b[l-1]這也時間復雜度為O(1)O(1)O(1)
但是問題來了,如果我們要既要修改數組中元素的值,有要進行上述區間查詢操作呢?
我們發現每次我們修改原數組中元素的值時間復雜度為O(1)O(1)O(1)但是如果修改前綴和數組中元素的值時間復雜度將會退化到O(n)O(n)O(n)
總結一下:
| 原數組 | O(1) | O(n) |
| 前綴和數組 | O(n) | O(1) |
我們可以發現如果需要單點更新和區間查詢兩種操作時間復雜度都是O(n)O(n)O(n)
什么是樹狀數組?
樹狀數組是一種便于進行單點更新和區間查詢的數據結構
樹狀數組相關操作
單點更新
我們對數組位置為x的元素加上c
局限性
我們很容易發現上述樹狀數組只適用于單點更新和區間查詢,但是如果是區間修改和單點查詢好像力不從心
差分在樹狀數組中的應用
告訴你個好消息如果有差分的介入,那么樹狀數組可以進行區間更新和單點查詢當然也可以進行更厲害的區間更新和區間查詢。
區間更新、單點查詢
我們把tree[]tree[]tree[]數組構造成一個差分數組還是看題吧
題目
給定長度為N的數列A,然后輸入M行操作指令。
第一類指令形如“C l r d”,表示把數列中第l~r個數都加d。
第二類指令形如“Q X”,表示詢問數列中第x個數的值。
對于每個詢問,輸出一個整數表示答案。
輸入格式
第一行包含兩個整數N和M。
第二行包含N個整數A[i]。
接下來M行表示M條指令,每條指令的格式如題目描述所示。
輸出格式
對于每個詢問,輸出一個整數表示答案。
每個答案占一行。
#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; }我們可以發現對于上述代碼即在建樹的過程中建成差分樹的形式即可
區間更新、區間查詢
原數組a[],對于區間更新我們可以維護一個差分數組b[]
如果我們維護數組a的前綴和我們可以發現有下面等式:
∑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
于是我們可以維護兩個差分樹狀數組tree1[]tree1[]tree1[]維護$ bi、、、 tree2[]維護維護維護i*bi$
給定一個長度為N的數列A,以及M條指令,每條指令可能是以下兩種之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示詢問 數列中第 l~r 個數的和。
對于每個詢問,輸出一個整數表示答案。
輸入格式
第一行兩個整數N,M。
第二行N個整數A[i]。
接下來M行表示M條指令,每條指令的格式如題目描述所示。
輸出格式
對于每個詢問,輸出一個整數表示答案。
每個答案占一行。
#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]; //維護b[i] 維護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; }樹狀數組應用
區間更新和區間查詢
逆序對:首先有一種求逆序對的方法:開一個數組cnt[n]?然后遍歷數組中的每一個數,在cnt[]數組中留下標記:比如數組中第i個元素為x,那就cnt[x]=1,所謂逆序即:我比你先出現而且比你大,對于上述例子所謂比你大即在[x+1,n]這個區間中的值,所謂比你先出現即在第i-1次遍歷時被標記過。轉化一下相當于求cntcntcnt數組中[x+1,n]區間內的和
第k小的數
總結
以上是生活随笔為你收集整理的【模板】一维树状数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国有哪些第一名
- 下一篇: OpenJudge1043 树上游戏(换