線段樹學習心得 poj3468

2016.5.10 神奇的博主回來了

今天,當然講的就是加強版的堆,其實算線段樹的說。。。

(還有你們看過的留留言好不好呀,謝謝了)

---------------------------

今天說的就是[[[加強版!!!]]]

內容大致就是成段更新,區間求和


也就是說,會用到高大上的lazy tag,那麼lazy tag有多lazy呢?神奇的是,如果每次區間全都加一個數,然後每次全都從下面到頂走一遍的話,妥妥的就炸。所以說,利用lazy tag標記的話,其實就是如果有增加的話,只是做一個標籤,在有必要的時候再調整,可以大幅縮小運行需要。


因爲需要,博主選擇一段一段的來寫,當然會在結尾貼上完整代碼。(所謂的最後一排/最頂上的,看不懂的話,請在上面的博文中找圖)

-----------------------------


因爲是一段一段的,就不加註釋符了。

首先,是建樹

struct Node

{

    int l,r;

    l所表示的是左子的管轄範圍就是說能管的區間的最左端

    r所表示的是右子的管轄範圍就是說能管的區間的最右端

    int sum2,lazy;

    sum也就是左子右子所管轄的sum的和

    lazy也就是lazy tag

    爲了便於下面的閱讀,我將在這裏簡單介紹一下<<&>>

    首先,<<代表的是二進制移位(往左移位)   >>代表的是二進制    往右移位,後面跟的數字就是移位的數。那移位又能幹什麼呢?比如:

2的二進制是00010是吧

移位一位     00100就是4

移位兩位     01000就是8

移位三位     10000就是16

可以看出往左移位1位是乘22位是乘43位呢,就是乘8等等,說是用起來比乘法要快一些。

反之就是一次除二

 } tree[N<<2]; 不是乘2,是乘4!理論上乘3就夠了,但是多一點總沒壞處,只要不炸就行。


下面就是建樹了。

我是瀟灑哥,瀟灑到根本不處理原數,直接用sum。其實最下面的sum就是原數所以其實更便捷了。

void build(int l,int r,int rt)  

這個建樹時使用的是遞歸建樹。

{

    tree[rt].l = l;

    tree[rt].r = r;  

完整程序中是build(1,n,1); 當然,不可能全都管轄範圍最左端全是1,右端也不可能全是n,這就是爲什麼遞歸,當lr都是初始的1n時,那麼我們在處理的一定是頂端第一個,那麼頂端第一個的左端右端就是1n 

    tree[rt].lazy=0;  

lazy置空,因爲還沒有涉及到成段更新的問題,初始化一下

    if(l == r)

    {

如果說左端和右端一樣了,那麼就說明是最下面一排的了,那麼就要開始輸入處理了。

        scanf("%d",&sum[rt]);

輸入sum,因爲最後一排的管轄總和就是自己本身的數

        return ;

    }

    build(lson);

遞歸左子,你一定會想,三個參數爲啥就一個了。。。這裏容我解釋一下,前面我用了宏

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

當然,前面<<>>也做了相應解釋,在這裏與或非中的或(|)爲什麼代表+1,我也不是很懂,所以如果有大神指點一下,謝謝。

2是左子,乘2+1是右子。涉及到位運算的相關知識,如果說是11,那麼肯定答案還是1。但是它前面帶着一個左移,那麼最後一位一定是0,那麼或1一定是+1。附上相關知識鏈接:http://baike.baidu.com/link?url=8gk3GFWZb0DQexRjbrdMjcRkept4HNaaR1EJ4HvMbgLm5COvKWmXPDoNtZFFayzKcS1mR1YnoKX_HHNd9iVTDq


    build(rson);

    PushUp(rt);

這個PushUp的作用,接下來揭曉。

}

-----------------------------

下面我會選擇先把易懂的查詢寫完。

int query(int l,int r,int rt)

還是遞歸,我似乎太喜歡遞歸了吧。。。

{

    if(l == tree[rt].l && r == tree[rt].r)

如果說正好的話

    {

        return tree[rt].sum2; 

那答案就是當前的和

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

這個PushDown的作用,接下來揭曉。

    int res = 0;

這個res就是最後的答案。

    int m=(tree[rt].l+tree[rt].r)/2;  

m所代表的就是一個分界線

就是說,比如說從35查詢

一共5個數

最開始的一定是1,所以左子是1,右子是5

m=(1+5)2=3

就是說跟快排(快速排序http://baike.baidu.com/link?url=zvoAGAi9YDykOgfvG__HCoAHVHDHoG8GP1uB9FyKwGpz3qYnRJjPPs0ZEXK1BYPtlkESo2_57WQDokkERB3tbjDLltgx4_BauWtkelIhGFbvosfCuh3vmuMAti6clzgjBrg5CvGC1AXfFS-T8EIGTb0mAzJUOFRLUvFf2WW9ex90V68KasgStbm0i1wmbrZj 

可以瞭解一下,也很有趣,也是從中間平均分)差不多,從中間分開一下。

    if(r <= m) res += query(l,r,rt<<1); 

 如果說右子還在m的左面,也就說所有的全部都在左邊的話,那就處理左子

    else if(l > m) res += query(l,r,rt<<1|1);

如果說左子還在m的右面,也就說說有的全部都在右面的話,那就處理右子

    else

    {

        res += query(l,m,rt<<1);

        res += query(m+1,r,rt<<1|1);

有左有右的話,就求兩邊的和

    }

    return res;

返回結果

}

-----------------------------

因爲在build裏面先出現的是PushUp,所以也沒什麼理由,任性決定先寫PushUp

void PushUp(int rt)

數據上推,也就是說(找圖看)上面的數據

{

    tree[rt].sum2=tree[2*rt].sum2+tree[2*rt+1].sum2;

當前的sum就是左子右子sum的和,和上面博文中的max差不太多

}

-----------------------------

 void PushDown(int rt,int m)

下發數據,m是左右子區間長度

{

    if(tree[rt].lazy==0) return;

如果說沒有要加的lazy那就省事了

    tree[rt*2].lazy+=tree[rt].lazy;

有的話,下發給左子

    tree[rt*2+1].lazy+=tree[rt].lazy;

下發給右子

    tree[rt*2].sum2+=tree[rt].lazy*(m-(m/2));

它左子加的和,一定是它能管到的數量的一半再乘上lazy

因爲二叉樹左子管到的一定是一半,而每個都是加lazy

但是和左子不同的是,雖然說在數學的角度,(m-(m/2))和(m/2)似乎是一樣的,但是說如果是單數這樣的話,就會有誤差,當然誤差會像滾雪球一樣越滾越大,所以說要特殊處理一下。

    tree[rt*2+1].sum2+=tree[rt].lazy*(m/2);

右子不同。

    tree[rt].lazy=0;

必須清空!!!看似微小的錯誤往往是致命的!如果鑰匙沒有置空的話,就會一直循環下去!!!一定要注意啊!


}

-----------------------------

還剩一個update

void update(int c,int l,int r,int rt)

{

又是遞歸。。。big boss來啦,就是所謂的成段更新

首先解決三個參數,第一個就是更新所加的數值,第二個第三個就是成段更新的範圍,第4rt當然就是當前位置不用多說

    if(tree[rt].l == l && r == tree[rt].r)

如果位置正好

    {

        tree[rt].lazy+=c;

當前位置的lazy直接就是更新所加的數

        tree[rt].sum2+=(int)c * (r-l+1); 

當然這個跟上面PushDown中的一模一樣,那爲什麼還要加呢?

當然這個問題愚蠢的樓主想了一會,本來就要改代碼了,發現其實如果在這種情況下根本不會調用PushDown,所以並不存在重複的情況,可以安心使用。

        return;

遞歸須謹慎啊!debug傷不起。。。

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

記得處理完把數據下發一下。

    int m=(tree[rt].l+tree[rt].r)/2;  

m還是分界

    if(r <= m) update(c,l,r,rt<<1);

其實這一段就跟query沒什麼區別啦,只不過是更新而已,判斷條件都沒變呢,還是自己去看吧

    else if(l > m) update(c,l,r,rt<<1|1);

    else

    {

        update(c,l,m,rt<<1);

        update(c,m+1,r,rt<<1|1);

    }

    PushUp(rt); 

上推一下數據。

}

-----------------------------

主函數非常簡單,在這裏也不再贅述,當然,前面所答應的完整代碼是一定會放的,首先,我找到了一道poj3468,先分析一下題目:

A Simple Problem with Integers

Time Limit: 5000MS Memory Limit: 131072K

Total Submissions: 89915 Accepted: 27990

Case Time Limit: 2000MS

Description

You have N integers, A1,A2, ... ,AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

Input

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.

The second line contains N numbers, the initial values of A1,A2, ... ,AN. -1000000000 ≤Ai ≤ 1000000000.

Each of the next Q lines represents an operation.

"C a b c" means adding c to each of Aa,Aa+1, ... ,Ab. -10000 ≤c ≤ 10000.

"Q a b" means querying the sum of Aa,Aa+1, ... ,Ab.

Output

You need to answer all Q commands in order. One answer in a line.

Sample Input

10 5

1 2 3 4 5 6 7 8 9 10

Q 4 4

Q 1 10

Q 2 4

C 3 6 3

Q 2 4

Sample Output

4

55

9

15

Hint

The sums may exceed the range of 32-bit integers.

Source

POJ Monthly--2007.11.25, Yang Yi

這道題所講的就是成段更新,區間求和。Q是區間查詢,求和。C是成段更新。值得注意的是,“The sums may exceed the range of 32-bit integers.”,用int要炸,但是poj的編譯系統只認__int 64Xcode中的lld似乎是不行的。

代碼:

#include <iostream>

#include <cstdio>

using namespace std;

const int N = 100005;

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

struct Node

{

    int l,r;

    __int64 sum,lazy;

    int mid()

    {

        return (l+r)>>1;

    }

    

} tree[N<<2];


void PushUp(int rt)

{

    tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;

    

}


void PushDown(int rt,int m)

{

    if(tree[rt].lazy)

    {

    

    tree[rt*2].lazy+=tree[rt].lazy;

    tree[rt*2+1].lazy+=tree[rt].lazy;

    tree[rt*2].sum+=tree[rt].lazy*(m - (m>>1));

    tree[rt*2+1].sum+=tree[rt].lazy*(m>>1);

    tree[rt].lazy=0;

}

}

void build(int l,int r,int rt)

{

    tree[rt].l = l;

    tree[rt].r = r;

    tree[rt].lazy=0;

    if(l == r)

    {

        scanf("%I64d",&tree[rt].sum);

        return ;

    }

    int m=tree[rt].mid();

    build(lson);

    build(rson);

    PushUp(rt);

    

}

void update(int c,int l,int r,int rt)

{

    if(tree[rt].l == l && r == tree[rt].r)

    {

        tree[rt].lazy+=c;

        tree[rt].sum+=(int)c * (r-l+1);

        return;

    }

    if(tree[rt].l == tree[rt].r) return;

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

    int m = tree[rt].mid();

    if(r <= m) update(c,l,r,rt<<1);

    else if(l > m) update(c,l,r,rt<<1|1);

    else

    {

        update(c,l,m,rt<<1);

        update(c,m+1,r,rt<<1|1);

    }

    PushUp(rt);

}

__int64 query(int l,int r,int rt)

{

    if(l == tree[rt].l && r == tree[rt].r)

    {

        return tree[rt].sum;

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

    int m=tree[rt].mid();

    __int64 res = 0;

    if(r <= m) res += query(l,r,rt<<1);

    else if(l > m) res += query(l,r,rt<<1|1);

    else

    {

        res += query(l,m,rt<<1);

        res += query(m+1,r,rt<<1|1);

    }

    return res;

}

int main()

{

    int n,m;

    while(~scanf("%d %d",&n,&m)) 

//按位取反

    {

        build(1,n,1);

        while(m--)

        {

            char ch[2];

            scanf("%s",ch);

            int a,b,c;

            if(ch[0] == 'Q')

            {

                scanf("%d %d", &a,&b);

                printf("%I64d\n",query(a,b,1));

            }


            else

            {

                scanf("%d %d %d",&a,&b,&c);

                update(c,a,b,1);

            }

        }

    }

    return 0;

}


-----------------------------

再給你們點福利,兩組測試數據奉上:

10 4

1 5 8 2 5 9 3 5 8 9

Q 3 6 

C 4 6 1

Q 5 8 

Q 4 5 


24

24

9

==================

29 5

2 5 2 3 6 7 3 5 8 3 7 5 4 1 2 6 8 3 6 3 2 5 7 9 8 6 3 1 5 

Q 3 19

C 1 28 5

Q 3 6 

C 3 6 8

Q 5 10


79

38

78

—————————————————————————————就這樣的,然後不懂的可以留言,還有一道線段樹poj3667也很適合,會在稍後奉上。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章