乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      線段樹從零開始

       木三水0vosidma 2019-05-08

      線段樹從零開始


      By 巖之痕


      一:為什么需要線段樹?

      題目一:

      10000個正整數(shù),編號1到10000,用A[1],A[2],A[10000]表示。

      修改:無

      統(tǒng)計:1.編號從L到R的所有數(shù)之和為多少? 其中1<= L <= R <= 10000.


      方法一:對于統(tǒng)計L,R ,需要求下標(biāo)從L到R的所有數(shù)的和,從L到R的所有下標(biāo)記做[L..R],問題就是對A[L..R]進(jìn)行求和。

      這樣求和,對于每個詢問,需要將(R-L+1)個數(shù)相加。


      方法二:更快的方法是求前綴和,令 S[0]=0, S[k]=A[1..k] ,那么,A[L..R]的和就等于S[R]-S[L-1],

      這樣,對于每個詢問,就只需要做一次減法,大大提高效率。



      題目二:

      10000個正整數(shù),編號從1到10000,用A[1],A[2],A[10000]表示。

      修改:1.將第L個數(shù)增加C (1 <= L <= 10000)

      統(tǒng)計:1.編號從L到R的所有數(shù)之和為多少? 其中1<= L <= R <= 10000.


      再使用方法二的話,假如A[L]+=C之后,S[L],S[L+1],,S[R]都需要增加C,全部都要修改,見下表。



      方法一 方法二

      A[L]+=C 修改1個元素 修改R-L+1個元素

      求和A[L..R] 計算R-L+1個元素之和 計算兩個元素之差


      從上表可以看出,方法一修改快,求和慢。 方法二求和快,修改慢。

      那有沒有一種結(jié)構(gòu),修改和求和都比較快呢?答案當(dāng)然是線段樹。



      二:線段樹的點(diǎn)修改


      上面的問題二就是典型的線段樹點(diǎn)修改。

      線段樹先將區(qū)間[1..10000]分成不超過4*10000個子區(qū)間,對于每個子區(qū)間,記錄一段連續(xù)數(shù)字的和。

      之后,任意給定區(qū)間[L,R],線段樹在上述子區(qū)間中選擇約2*log2(R-L+1)個拼成區(qū)間[L,R]。

      如果A[L]+=C ,線段樹的子區(qū)間中,約有l(wèi)og2(10000)個包含了L,所以需要修改log2(10000)個。


      于是,使用線段樹的話,

      A[L]+=C 需要修改log2(10000) 個元素

      求和A[L...R]需要修改2*log2(R-L+1) <= 2 * log2(10000) 個元素。

      log2(10000) < 14 所以相對來說線段樹的修改和操作都比較快。




      問題一:開始的子區(qū)間是怎么分的?

      首先是講原始子區(qū)間的分解,假定給定區(qū)間[L,R],只要L < R ,線段樹就會把它繼續(xù)分裂成兩個區(qū)間。

      首先計算 M = (L+R)/2,左子區(qū)間為[L,M],右子區(qū)間為[M+1,R],然后如果子區(qū)間不滿足條件就遞歸分解。

      以區(qū)間[1..13]的分解為例,分解結(jié)果見下圖:




      問題二:給定區(qū)間【L,R】,如何分解成上述給定的區(qū)間?

      對于給定區(qū)間[2,12]要如何分解成上述區(qū)間呢?


      分解方法一:自下而上合并——利于理解

      先考慮樹的最下層,將所有在區(qū)間[2,12]內(nèi)的點(diǎn)選中,然后,若相鄰的點(diǎn)的直接父節(jié)點(diǎn)是同一個,那么就用這個父節(jié)點(diǎn)代替這兩個節(jié)點(diǎn)(父節(jié)點(diǎn)在上一層)。這樣操作之后,本層最多剩下兩個節(jié)點(diǎn)。若最左側(cè)被選中的節(jié)點(diǎn)是它父節(jié)點(diǎn)的右子樹,那么這個節(jié)點(diǎn)會被剩下。若最右側(cè)被選中的節(jié)點(diǎn)是它的父節(jié)點(diǎn)的左子樹,那么這個節(jié)點(diǎn)會被剩下。中間的所有節(jié)點(diǎn)都被父節(jié)點(diǎn)取代。對最下層處理完之后,考慮它的上一層,繼續(xù)進(jìn)行同樣的處理。


      下圖為n=13的線段樹,區(qū)間[2,12],按照上面的敘述進(jìn)行操作的過程圖:


      由圖可以看出:在n=13的線段樹中,[2,12]=[2] + [3,4] + [5,7] + [8,10] + [11,12] 。


      分解方法二:自上而下分解——利于計算


      首先對于區(qū)間[1,13],計算(1+13)/2 = 7,于是將區(qū)間[2,12]“切割”成了[2,7]和[8,12]。


      其中[2,7]處于節(jié)點(diǎn)[1,7]的位置,[2,7] < [1,7] 所以繼續(xù)分解,計算(1+7)/2 = 4, 于是將[2,7] 切割成[2,4]和[5,7]。


      [5,7]處于節(jié)點(diǎn)[5,7]的位置,所以不用繼續(xù)分解,[2,4]處于區(qū)間[1,4]的位置,所以繼續(xù)分解成[2]和[3,4]。


      最后【2】 < 【1,2】,所以計算(1+2)/2=1 ,將【2】用1切割,左側(cè)為空,右側(cè)為【2】


      當(dāng)然程序是遞歸計算的,不是一層一層計算的,上圖只表示計算方法,不代表計算順序。



      問題三:如何進(jìn)行區(qū)間統(tǒng)計?

      假設(shè)這13個數(shù)為1,2,3,4,1,2,3,4,1,2,3,4,1. 在區(qū)間之后標(biāo)上該區(qū)間的數(shù)字之和:


      如果要計算[2,12]的和,按照之前的算法:

      [2,12]=[2] + [3,4] + [5,7] + [8,10] + [11,12]

        29 = 2 + 7 + 6 + 7 + 7

      計算5個數(shù)的和就可以算出[2,12]的值。


      問題四:如何進(jìn)行點(diǎn)修改?

      假設(shè)把A[6]+=7 ,看看哪些區(qū)間需要修改?[6],[5,6],[5,7],[1,7],[1,13]這些區(qū)間全部都需要+7.其余所有區(qū)間都不用動。

      于是,這顆線段樹中,點(diǎn)修改最多修改5個線段樹元素(每層一個)。

      下圖中,修改后的元素用藍(lán)色表示。


      問題五:存儲結(jié)構(gòu)是怎樣的?


      線段樹是一種二叉樹,當(dāng)然可以像一般的樹那樣寫成結(jié)構(gòu)體,指針什么的。

      但是它的優(yōu)點(diǎn)是,它也可以用數(shù)組來實(shí)現(xiàn)樹形結(jié)構(gòu),可以大大簡化代碼。

      數(shù)組形式適合在編程競賽中使用,在已經(jīng)知道線段樹的最大規(guī)模的情況下,直接開足夠空間的數(shù)組,然后在上面建立線段樹。

      怎么用數(shù)組來表示一顆二叉樹呢?假設(shè)某個節(jié)點(diǎn)的編號為v,那么它的左子節(jié)點(diǎn)編號為2*v,右子節(jié)點(diǎn)編號為2*v+1。

      然后規(guī)定根節(jié)點(diǎn)為1.這樣一顆二叉樹就構(gòu)造完成了。通常2*v在代碼中寫成 v<<1 。 2*v+1寫成 v<<1|1 。


      問題六:代碼中如何實(shí)現(xiàn)?

      (0)定義:


      #define maxn 100007 //元素總個數(shù)  

      int Sum[maxn<<2];//Sum求和  

      int A[maxn],n;//存原數(shù)組數(shù)據(jù)下標(biāo)[1,n]   

      (1)建樹:


      //PushUp函數(shù)更新節(jié)點(diǎn)信息 ,這里是求和  

      void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}  

      //Build函數(shù)建樹   

      void Build(int l,int r,int rt){ //l,r表示當(dāng)前節(jié)點(diǎn)區(qū)間,rt表示當(dāng)前節(jié)點(diǎn)編號  

          if(l==r) {//若到達(dá)葉節(jié)點(diǎn)   

              Sum[rt]=A[l];//儲存數(shù)組值   

              return;  

          }  

          int m=(l+r)>>1;  

          //左右遞歸   

          Build(l,m,rt<<1);  

          Build(m+1,r,rt<<1|1);  

          //更新信息   

          PushUp(rt);  

      }  


      (2)點(diǎn)修改:


      假設(shè)A[L]+=C:

      void Update(int L,int C,int l,int r,int rt){//l,r表示當(dāng)前節(jié)點(diǎn)區(qū)間,rt表示當(dāng)前節(jié)點(diǎn)編號  

          if(l==r){//到葉節(jié)點(diǎn),修改   

              Sum[rt]+=C;  

              return;  

          }  

          int m=(l+r)>>1;  

          //根據(jù)條件判斷往左子樹調(diào)用還是往右   

          if(L <= m) Update(L,C,l,m,rt<<1);  

          else Update(L,C,m+1,r,rt<<1|1);  

          PushUp(rt);//子節(jié)點(diǎn)更新了,所以本節(jié)點(diǎn)也需要更新信息   

      }   

      點(diǎn)修改其實(shí)可以寫的更簡單,只需要把一路經(jīng)過的Sum都+=C就行了,不過上面的代碼更加規(guī)范,在題目更加復(fù)雜的時候,按照格式寫更不容易錯。




      (3)區(qū)間查詢(本題為求和):


      詢問A[L..R]的和

      注意到,整個函數(shù)的遞歸過程中,L,R是不變的。

      首先如果當(dāng)前區(qū)間[l,r]在[L,R]內(nèi)部,就直接累加答案

      如果左子區(qū)間與[L,R]有重疊,就遞歸左子樹,右子樹同理。

      int Query(int L,int R,int l,int r,int rt){//L,R表示操作區(qū)間,l,r表示當(dāng)前節(jié)點(diǎn)區(qū)間,rt表示當(dāng)前節(jié)點(diǎn)編號  

          if(L <= l && r <= R){  

              //在區(qū)間內(nèi),直接返回   

              return Sum[rt];  

          }  

          int m=(l+r)>>1;  

          //左子區(qū)間:[l,m] 右子區(qū)間:[m+1,r] 求和區(qū)間:[L,R]

          //累計答案  

          int ANS=0;  

          if(L <= m) ANS+=Query(L,R,l,m,rt<<1);//左子區(qū)間與[L,R]有重疊,遞歸

          if(R > m) ANS+=Query(L,R,m+1,r,rt<<1|1); //右子區(qū)間與[L,R]有重疊,遞歸

          return ANS;  

      }   


        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多