樹狀數組


樹狀數組

一、用處

       有時候題目會要求維護一個數組的前綴和,朴素調整的話最壞是O(n)的復雜度

       而當我們學會了 “樹狀數組” ,他的修改與求和都是O(logn)的

常見用於:

(1)單點修改,區間查詢

(2)區間修改,單點查詢(差分實現)

 

 

二、基本思想

       任意一個正整數都可以被 “二進制分解” 

       比如區間 [1,n] 可以分解成 logx個小區間

       樹狀數組就是就是基於以上操作的一種數據結構,基本用途是維護前綴和。對於區間[1, x ] ,樹狀數組將他分解為logx個子區間,從而滿足快速詢問區間和。

 

三、基本算法

       子區間的共同特點是:若區間結尾為R,則區間長度就等於R的“二進制分解”下的最小二次冪,設為lowbit(R)

       對於給定的序列A,建立一個數組c,c[x]保存序列A的區間 [ x-lowbit(x)+1,x ] 中所有數字的和

你看下面這個圖:

 

該結構滿足以下性質:

(1)每個內部節點c[x]保存以他為根的子樹中所有葉節點的和

(2)每個內部節點c[x]的子節點數等於lowbit(x)的大小

(3)除數根外,每個內部節點c[x]的父節點是c[x+lowbit(x)]

(4)樹的深度為O(logN)

 

 

1.求lowbit(x)

int lowbit(int x)
{
    return x&-x;
}
lowbit(x)

 

2.單點修改

   當我們修改了單點的值,與它相關的父節點的值也會相應的發生改變,上傳維護,由子及父

void updata(int x,int v)
{
    while(x<=n)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}
單點修改

 

3.查詢前綴和

   由父及子

int sum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
查詢前綴和

 

4.區間求和

   Σx~y = sum(y) - sum(x-1)

 

5.擴展(多維樹狀數組)

如果有n*m的二維數組a,樹狀數組為c,那么單點修改和求前綴和就有以下操作:

int updata(int x,int y,int z)
{
    int i=x;
    while(i<=n)
    {
        int j=y;
        while(j<=m)
        {
            c[i][j]+=z;
            j+=lowbit(j);
        }
        i+=lowbit(i);
    }
}
二維樹狀數組單點修改
int sum(int x,int y)
{
    int ans=0,i=x;
    while(i>0)
    {
        int j=y;
        while(j>0)
        {
            ans+=c[i][j];
            j-=lowbit(j);
        }
        i-=lowbit(i);
    }
    return ans;
}
二維樹狀數組求前綴和

 

6.注意事項

樹狀數組能處理的是下標為1~n的數組,下標絕對不能為0,lowbit(0)=0,這樣會陷入死循環

 

 

四、典型例題

(1)單點修改,區間查詢

       P3374 【模板】樹狀數組 1

非常正宗的板子題了

#include<bits/stdc++.h>

using namespace std;

const int maxn=5e5+10;
int n,m,opr,x,y,k;
int c[maxn];

inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int lowbit(int x)
{
    return x&-x;
}

void updata(int x,int v)
{
    while(x<=n)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}

int sum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        k=read();
        updata(i,k);
    }
    
    for(int i=1;i<=m;i++)
    {
        opr=read();x=read();y=read();
        if(opr==1) updata(x,y);
        if(opr==2) printf("%d\n",sum(y)-sum(x-1));
    }
    
    return 0;
}
樹狀數組<單點修改,區間查詢>

 

(2)區間修改,單點查詢

P3368 【模板】樹狀數組 2

 

題解

這里是用差分來實現

什么是差分??

給出一個數列 A1  A2  A3  A4  A5 。。。。An

用數組 c[ i ] 來記錄A 與 A i-1的差,即 c[ i ] = A[ i ] - A[ i-1 ]

 

那么當我們想要修改區間 [ x,y ]的值的時候,區間里每個數都加上相同的數字,c[i+1]~c[j]都是不變的,改變的只是 c[ i ] 和 c[ j+1 ] ,由於是區間加,c[ i ] 自然就變大了,c[ j+1 ] 自然就變小了

這時用二維數組維護差分數組就行了,每次區間修改只需要改兩個值

 

單點查詢呢?  A x = Σ c[ i ] (i=1~i)

#include<bits/stdc++.h>

using namespace std;

const int maxn=5e5+10;
int n,m,opr,x,y,k;
int c[maxn],a[maxn];

inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int lowbit(int x)
{
    return x&-x;
}

void updata(int x,int v)
{
    while(x<=n)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}

int sum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        updata(i,a[i]-a[i-1]);
    }
       
    for(int i=1;i<=m;i++)
    {
        opr=read();
        if(opr==1)
        {
            x=read();y=read();k=read();
            updata(x,k);
            updata(y+1,-k);
        } 
        if(opr==2)
        {
            x=read();
            printf("%d\n",sum(x));
        } 
    }
    
    return 0;
}
樹狀數組 <差分>

 

 

 五、后記

能用樹狀數組做的題,線段樹也能做;

但能用線段樹做的,樹狀數組不一定能做。

它比線段樹優秀是什么情況呢??

  • 線段樹常數過大時
  • 線段樹功能過多時

樹狀數組可求的所有問題必須存在逆元

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM