1. 程式人生 > >樹秀於林風必摧之——線段樹

樹秀於林風必摧之——線段樹

線段 strong pda -1 自己的 我們 不重復 ebe wid

關於線段樹,其實我一開始也是很懵的,但看久了也就習慣了。

  以下是我對線段樹的一點理解,寫得不好,也請各位看官見諒。

  搜狗定義:線段樹(Segment Tree)是一種二叉搜索樹,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。

  定義還是很顯然的。

  那麽線段樹都能做些什麽呢?

  在n個數中有m個詢問,詢問如下:

  1.把q這個數改成v O(logn);

  2.求在1~n這個區間的和.

  接下來我們講原理(當然原理也是我自己的理解,可能不是正解,但我想,線段樹這個東西大概就是這樣的吧)

  首先看圖

  技術分享

  下面我們將原理

  1.它是用“二進制”存儲的

  什麽是“二進制”存儲?

  眾所周知,用二的倍數可以表示所有的數

  例:13=1+4+8;

  (具體原理請參考二進制和十進制的轉換)

  那麽線段樹也是這樣:

  如:我們要查找(2,5)這個區間,那它就是2+(3,4)+5所代表的數(區間和)

  2.它是“二分查找”(當然不是嚴格意義上的)

  二分查找不再贅述

  例:當我們要把6這個位置上所在的數改為★,那我們一定是這樣查找的:

  (1,8)->(5,8)->(5,6)->(6)->(★)

  那麽怎麽實現呢?我大概總結了一下幾個步驟:

  1.建立線段樹

  2.查找位置+動作

  具體代碼如下

  

int a[maxn];//每個數的值 
int sum[maxn*4];//區間和 


void update(int rt)
{
    sum[rt]=sum[rt*2]+sum[rt*2+1];//前綴和思想 
} 

void build(int l,int r,int rt)//建立樹,左孩子,右孩子,根
{
    if(l==r)
    {
        sum[rt]=a[l];
        return ;//邊界 
    }
    int m=(l+r)/2;
    build(1,m,rt*2);
    build(m+1,r,rt*2+1);
    update(rt);
//合並兩個兒子 }

//如果不理解左孩子右孩子為什麽要那樣寫的,看這裏:

技術分享

以根節點為例:(1)

左孩子:1*2=2;

右孩子:1*2+1=3;

這樣就能保證樹在數組裏存滿且不重復。

void modify(int l,int r,int rt,int p,int v)//將p的位置上的數改為v 
{
    if(l==r)
    {
        sum[rt]=v;
        return ;//找到這個數了,改值。當然我們也會順便把與它相關的所有值都改掉 
    }
    int m=(l+r)/2;
    if(p<=m)
        modify(1,m,rt*2,p,v);
    else
        modify(m+1,r,rt*2+1,p,v);//二分查找 
    update(rt);
} 

int query(int l.int r,int rt,int nowl,int nowr)//詢問(nowl,nowr)這個區間和 
{
    if(nowl<=1&&r<=nowr)//邊界 
        return sum[rt];
    int m=(l+r)/2;
    int ans=0;
    if(nowl<=m)ans+=query(1,m,rt*2,nowl,nowr);
    if(m<nowr)ans+=query(m+1,r,rt*2+1,nowl,nowr);//查找求和 
    return ans;
}

這是兩次詢問。

主函數的話就依據情況調用這三個函數就好了

那我要講的,大概就是這些了。

byebey!^_^

樹秀於林風必摧之——線段樹