日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【模板】一维树状数组

發布時間:2023/12/3 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【模板】一维树状数组 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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)

什么是樹狀數組?

樹狀數組是一種便于進行單點更新區間查詢的數據結構

樹狀數組相關操作

  • 二進制中最后一個1——lowbitlowbitlowbit
  • int lowbit(int x){return x&-x;}
  • 單點更新

    我們對數組位置為x的元素加上c

  • //樹狀數組為tree,數組元素個數為n,數組下標從0開始void add(int x,int c){for(;x<=n;x+=lowbit(x)) tree[x]+=c;}
  • 區間求和
  • //求出[1,x]數組中的總和即前綴和int sum(int x){int res=0;for(;x;x-=lowbit(x)) res+=tree[x];return res;}

    局限性

    我們很容易發現上述樹狀數組只適用于單點更新區間查詢,但是如果是區間修改單點查詢好像力不從心

    差分在樹狀數組中的應用

    告訴你個好消息如果有差分的介入,那么樹狀數組可以進行區間更新單點查詢當然也可以進行更厲害的區間更新區間查詢

    區間更新、單點查詢

    我們把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=1x?ai?=i=1x?j=1i?bi?=i=1x?(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=1x?ai?=(x+1)i=1x?bi??i=1x?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小的數

  • 總結

    以上是生活随笔為你收集整理的【模板】一维树状数组的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。