1. 程式人生 > >簡單易懂線段樹-第一步建樹-腦袋蒙的來瞧瞧

簡單易懂線段樹-第一步建樹-腦袋蒙的來瞧瞧

看了網上好多好多好多的程式碼,能告訴我為啥都用 '>>'這些符號寫的麼...是感覺很好看麼...(確實挺漂亮問題是本來就很蒙的好吧!)還有啊,確實是我實力太差define 定義左右兒子那每次看到函式裡就蒙了,我都替換成正常的了

不過幸好在頭腦清醒的時候弄明白了一些,雖然現在查詢那還是不太懂

好啦好啦不多說了

首先我先把正常有加減乘除符號的程式碼給貼出來,當初我就是卡在這了,幾乎沒見到正常的程式碼= =本來就濛濛的

有這裡卡住的看到就懂了就不用往下看了,用hdu1166舉例子

//hdu 1166 敵兵佈陣
#include<cstdio>
#include<cstring>

int sum[55555*4];

void pushup(int rt)
{
    sum[rt] = sum[rt*2]+sum[rt*2+1];
}

void build(int l, int r, int rt)
{
    if(l == r)
    {
        scanf("%d", &sum[rt]);
        return ;
    }
    int m = (l+r)/2;
    build(l, m, rt*2);
    build(m+1, r, rt*2+1);
    pushup(rt);
}

void update(int p, int add, int l, int r, int rt)
{
    if(l == r)
    {
        sum[rt]+=add;
        return;
    }
    int m = (l+r)/2;
    if(p <= m)update(p, add, l, m, rt*2);
    else update(p, add, m+1, r, rt*2+1);
    pushup(rt);
}

int query(int L, int R, int l, int r, int rt)
{
    if(L <= l && r <= R)return sum[rt];
    int m = (l+r)/2;
    int ret = 0;
    if(L <= m) ret += query(L, R, l, m, rt*2);
    if(R > m)ret += query(L, R, m+1, r, rt*2+1);
    return ret;
}

int main()
{
    int tt, n, i, j, k, x, y;
    scanf("%d", &tt);
    for(int cas = 1; cas <= tt; ++cas)
    {
        printf("Case %d:\n", cas);
        scanf("%d", &n);
        build(1, n, 1);
        char ch[10];
        while(scanf("%s", ch),ch[0]-'E')
        {
            scanf("%d%d", &x, &y);
            if(ch[0]=='A')update(x, y, 1, n, 1);
            else if(ch[0]=='S')update(x, -y, 1, n, 1);
            else printf("%d\n",query(x, y, 1, n, 1));
        }
    }
    return 0;
}
接下來就是我個人看的理解了,有講錯了歡迎指出

1、首先既然是線段樹 既然叫做‘樹’ 就說明是樹狀的對吧,這樣我們 怎麼用這樣的樹 來記錄各種值呢

答案就是 用樹葉來記錄每個兵營的人數(我們用這道題舉例子, 大家快去讀一讀是中文題欸)

然後每個節點 就能記錄下面葉子的和,這樣一層一層的記錄就好查了

可是大家還是很蒙,怎麼記錄,怎麼查詢,你光這麼說我也不知道啊,思想誰都會啊!

別急,才開始,且聽我用我的方式慢慢道來


2、開始建立樹,函式 void build(int l, int r, int rt) 我們都需要準備什麼呢? 我這裡不用結構體,就用最直白的

準備: rt, 這個是記錄當前結點標號的,就是每個節點的下標(index)

   l, r, 這個是 當前第 rt 節點  所記錄的 左邊 和 右邊, 就是l到r之間的和

           在函式最外面還需要一個sum[ ]陣列,用來記錄每個節點記錄的值

接下來是一個關鍵點:當l==r的時候代表什麼呢?

對!當l==r 說明我們當前第rt個點只有記錄了它自己,因為它下面沒有了分支所以他是葉子!!

所以build函式一開始就是這樣

<pre name="code" class="cpp"><pre name="code" class="cpp">void build(int l, int r, int rt)
{
    if(l == r)//當前發現是葉子,也就是每個兵營
    {
        scanf("%d", &sum[rt]);//那麼我就輸入一個要記錄的兵營人數
        return;//當前找到最底部的葉子了,可以返回了
    }
    函式還沒寫完
}
為什麼葉子還用sum記錄人數呢,你想想l==r是不是說明也是個區間和?只不過這個區間只有一個值,所以沒必要開其他陣列混淆視聽啦

那麼每次我怎麼找到l == r啊?你去看主函式 ,是不是寫作build(1, n, 1); n是我們兵營數量

也就是說我開始時候l從1開始,r從n開始,1到n就是 rt == 1 記錄的區間

然後函式發現它不是葉子,就繼續執行

void build(int l, int r, int rt)
{
    if(l == r)
    {
        scanf("%d", &sum[rt]);
        return;
    }
    int m = (l+r)/2; //把區間對半分
    build(l, m, rt*2);//從l -> m 左半遞迴
    build(m+1, r, rt*2+1);//從 m+1 -> r 右半遞迴
    pushup(rt);
}

就這樣,不是葉子就對半分,然後左遞迴,右遞迴,記住每次標記rt需要*2和*2+1,這是二叉樹的規律嘛,我們利用了這個規律

pushup用來每次遞迴已經確立的左子樹和右子樹的和

void pushup(int rt)
{
    sum[rt] = sum[rt*2]+sum[rt*2+1];
}

到這裡建樹就基本完成了,我實力太差只能寫成這樣了,我的想法就是理解如何寫出的程式碼,然後用理解的方式背下來,

至少能記住很久,至於真正理解這樣的思想就靠題和時間 還有 之後的返工了,我這裡只是講了怎麼理解寫出來的

看看有木有人看,有木有人理解,要是需要我再寫新增add和查詢query,其實這倆和建樹異曲同工啦

我也想成為厲害的人,給自己個交代我也是能做到很厲害的人,否則一直會籠罩在沒有自信之中

大家一起努力啦啦啦

最後提示一下如果用cin和cout一定會超時的,我試著用

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    剩下的主函式接著寫
}

我試著用這兩句話加速cin和cout 才勉強980多ms踩過,而且解除和scanf的繫結之後一開始輸出Case什麼的會報錯

還需要拆開輸出,很費勁,就算我不用endl也才省了20多ms(之前看貼吧有人說endl費時),所以老老實實用scanf和printf吧!