uscao 线段树成段更新操作及Lazy思想(POJ3468解题报告)
線段樹成段更新操作及Lazy思想(POJ3468解題報告)
標簽:?treequerybuildn2cstruct 2011-11-03 20:37?5756人閱讀?評論(0)?收藏?舉報 ?分類: ?版權聲明:本文為博主原創文章,未經博主允許不得轉載。
就直接那POJ上面的例題來說吧,http://poj.org/problem?id=3468。
此題題意很好懂:
?給你N個數,Q個操作,操作有兩種,‘Q a b ’是詢問a~b這段數的和,‘C a b c’是把a~b這段數都加上c。
需要用到線段樹的,update:成段增減,query:區間求和
介紹Lazy思想:lazy-tag思想,記錄每一個線段樹節點的變化值,當這部分線段的一致性被破壞我們就將這個變化值傳遞給子區間,大大增加了線段樹的效率。
在此通俗的解釋我理解的Lazy意思,比如現在需要對[a,b]區間值進行加c操作,那么就從根節點[1,n]開始調用update函數進行操作,如果剛好執行到一個子節點,它的節點標記為rt,這時tree[rt].l == a && tree[rt].r == b 這時我們可以一步更新此時rt節點的sum[rt]的值,sum[rt] += c * (tree[rt].r - tree[rt].l + 1),注意關鍵的時刻來了,如果此時按照常規的線段樹的update操作,這時候還應該更新rt子節點的sum[]值,而Lazy思想恰恰是暫時不更新rt子節點的sum[]值,到此就return,直到下次需要用到rt子節點的值的時候才去更新,這樣避免許多可能無用的操作,從而節省時間 。
下面通過具體的代碼來說明之。(此處的函數名和宏學習了小HH的代碼風格)
在此先介紹下代碼中的函數說明:
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
宏定義左兒子lson和右兒子rson,貌似用宏的速度要慢。
PushUp(rt):通過當前節點rt把值遞歸向上更新到根節點
PushDown(rt):通過當前節點rt遞歸向下去更新rt子節點的值
rt表示當前子樹的根(root),也就是當前所在的結點
[cpp]?view plaincopy print?
- __int64?sum[N<<2],add[N<<2];??
- struct?Node??
- {??
- ????int?l,r;??
- ????int?mid()??
- ????{??
- ????????return?(l+r)>>1;??
- ????}??
- }?tree[N<<2];??
tree[].l tree[].r分別表示某個節點的左右區間,這里的區間是閉區間
下面直接來介紹update函數,Lazy操作主要就是用在這里
[cpp]?view plaincopy print?
- void?update(int?c,int?l,int?r,int?rt)//表示對區間[l,r]內的每個數均加c,rt是根節點??
- {??
- ????if(tree[rt].l?==?l?&&?r?==?tree[rt].r)??
- ????{??
- ????????add[rt]?+=?c;??
- ????????sum[rt]?+=?(__int64)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);??
- }??
if(tree[rt].l == l && r == tree[rt].r) 這里就是用到Lazy思想的關鍵時刻 正如上面說提到的,這里首先更新該節點的sum[rt]值,然后更新該節點具體每個數值應該加多少即add[rt]的值,注意此時整個函數就運行完了,直接return,而不是還繼續向子節點繼續更新,這里就是Lazy思想,暫時不更新子節點的值。
那么什么時候需要更新子節點的值呢?答案是在某部分update操作的時候需要用到那部分沒有更新的節點的值的時候,這里可能有點繞口。這時就掉用PushDown()函數更新子節點的數值。
[cpp]?view plaincopy print?
- void?PushDown(int?rt,int?m)??
- {??
- ????if(add[rt])??
- ????{??
- ????????add[rt<<1]?+=?add[rt];??
- ????????add[rt<<1|1]?+=?add[rt];??
- ????????sum[rt<<1]?+=?add[rt]?*?(m?-?(m>>1));??
- ????????sum[rt<<1|1]?+=?add[rt]?*?(m>>1);??
- ????????add[rt]?=?0;//更新后需要還原??
- ????}??
- }??
接著就是update操作的三個if語句了,這里我曾經一直不理解,多虧nyf隊友的指點,借此感謝之。
下面再解釋query函數,也就是用這個函數來求區間和
[cpp]?view plaincopy print?
- __int64?query(int?l,int?r,int?rt)??
- {??
- ????if(l?==?tree[rt].l?&&?r?==?tree[rt].r)??
- ????{??
- ????????return?sum[rt];??
- ????}??
- ????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;??
- }??
第一個if還是區間的判斷和前面update的一樣,到這里就可以知道答案了,所以就直接return。
接下來的查詢就需要用到rt子節點的值了,由于我們用了Lazy操作,這段的數值還沒有更新,因此我們需要調用PushDown函數去更新之,滿足if(add[rt])就說明還沒有更新。
到這里整個Lazy思想就算介紹結束了,可能我的語言組織不是很好,如果有不理解的地方可以給我留言,我再解釋大家的疑惑。
PS:今天總算是對線段樹入門了。
這里推薦一下,完全版線段樹網址
下面貼出POJ3468完整的代碼http://www.notonlysuccess.com/index.php/segment-tree-complete/,這里面有很飄逸的線段樹代碼,表示其update和query寫的很巧妙,代碼量也比較少,大家可以去學習。
[cpp]?view plaincopy print?
- #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??
- ??
- __int64?sum[N<<2],add[N<<2];??
- struct?Node??
- {??
- ????int?l,r;??
- ????int?mid()??
- ????{??
- ????????return?(l+r)>>1;??
- ????}??
- }?tree[N<<2];??
- ??
- void?PushUp(int?rt)??
- {??
- ????sum[rt]?=?sum[rt<<1]?+?sum[rt<<1|1];??
- }??
- ??
- void?PushDown(int?rt,int?m)??
- {??
- ????if(add[rt])??
- ????{??
- ????????add[rt<<1]?+=?add[rt];??
- ????????add[rt<<1|1]?+=?add[rt];??
- ????????sum[rt<<1]?+=?add[rt]?*?(m?-?(m>>1));??
- ????????sum[rt<<1|1]?+=?add[rt]?*?(m>>1);??
- ????????add[rt]?=?0;??
- ????}??
- }??
- ??
- void?build(int?l,int?r,int?rt)??
- {??
- ????tree[rt].l?=?l;??
- ????tree[rt].r?=?r;??
- ????add[rt]?=?0;??
- ????if(l?==?r)??
- ????{??
- ????????scanf("%I64d",&sum[rt]);??
- ????????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)??
- ????{??
- ????????add[rt]?+=?c;??
- ????????sum[rt]?+=?(__int64)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?sum[rt];??
- ????}??
- ????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;??
- }??
總結
以上是生活随笔為你收集整理的uscao 线段树成段更新操作及Lazy思想(POJ3468解题报告)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 永远有多远是哪首歌啊?
- 下一篇: 数论倒数