線段樹之延時標記(區間修改)及lazy思想

2021-08-18 16:52:57 字數 2959 閱讀 7913

1.最簡單的方法是:在主函式中新增乙個迴圈

進行 r-l+1次單點修改實現區間修改,對於單個元素修改時間複雜度為 o(log2(n))

所以對於單個區間修改的時間複雜度為 o(n*log(n)),甚至比樸素的模擬演算法還慢

for(int p=l;p<=r;++p)

updatetree(p,value);

2.延時標記 lazy tag

延時標記就是在遞迴的過程中,如果當前區間被需要,修改的區間完全覆蓋,那麼就要停止遞迴,並且在上面做乙個標記 

3.但是這個資訊沒有更新到每個元素(即葉子結點),下次查詢的時候可能沒法得到足夠的資訊。之前在乙個區間上打了乙個標記,這個標記不僅是這個結點的性質,此性質作用於整個子樹中,假設我們另乙個查詢中包含了,當前區間的子孫區間,這個標記也要對之後的查詢產生影響

1.如果對線段樹進行單點更新,都是在葉子節點中實現,不會對後續節點產生影響

2.如果當前區間被需要修改的目標區間完全覆蓋,打乙個標記 

如果下一次的查詢或更改包含此區間,那麼將這個標記分解,並傳遞給左右兒子

3.延時標記在需要時,才向下傳遞資訊,如果沒有用到,則不再進行操作

為了完成這種操作,可以在結構體中增加乙個add陣列儲存區間的延時變化量

4.通俗的解釋 lazy的意思,比如現在需要對[a,b]區間進行加 c操作,

那麼就從根節點[1,n]開始呼叫update函式進行操作,如果剛好執行到乙個子節點

它的節點標記為 rt,這時tree[rt].l==l&&tree[rt].r==r這時我們可以進一步

更新此時rt節點的sum[rt]的值,sum[rt]+=(ll)c*(r-l+1);

5.關鍵的地方:如果此時按照常規的線段樹的update操作,這時候還應該更新

rt子節點的sum值,而lazy思想恰恰是暫時不更新rt子節點的sum值,

到此時就 return,直到下一次需要用到rt子節點的值得時候才去更新,

這樣避免許多無用的操作,從而節省時間

6.pushup(rt);通過當前節點rt把值遞迴向上更新到根節點

pushdown(rt);通過當前節點rt遞迴向下去更新rt子節點的值

rt表示當前子樹的根,也就是當前所在的節點 

1.定義結構體來儲存每個節點的子節點數值的總和,

add用來記錄該節點的每個數值應該加多少

tree[i].l,tree[i].r分別表示某個節點的左右區間,都是閉區間 

__int64 sum[n<<2],add[n<<2];

struct node

} tree[n<<2];

2.pushup(rt);通過當前節點rt把值遞迴向上更新到根節點

void pushup(int rt)

3.pushdown(rt);通過當前節點rt遞迴向下去更新rt子節點的值

rt表示當前子樹的根,也就是當前所在的節點 

void pushdown(int rt,int m)

}

4.建立線段樹

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

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

build(lson);

build(rson);

pushup(rt);

}

5.update函式,lazy思想主要用於這裡

通俗的解釋 lazy的意思,比如現在需要對[a,b]區間進行加 c操作,

那麼就從根節點[1,n]開始呼叫update函式進行操作,如果剛好執行到乙個子節點

它的節點標記為 rt,這時 tree[rt].l==l&&tree[rt].r==r這時我們可以進一步

更新此時rt節點的sum[rt]的值,sum[rt]+=(ll)c*(r-l+1);

關鍵的地方:如果此時按照常規的線段樹的update操作,這時候還應該更新

rt子節點的sum值,而lazy思想恰恰是暫時不更新rt子節點的sum值,

到此時就 return,直到下一次需要用到rt子節點的值得時候才去更新,

這樣避免許多無用的操作,從而節省時間

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

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

pushup(rt);

}

6.query函式,也就是用這個函式來求區間和

第乙個if還是區間的判斷和前面update的一樣,到這裡就可以知道答案了,所以就直接return。

接下來的查詢就需要用到rt子節點的值了,由於我們用了lazy操作,這段的數值還沒有更新,因此我們需要呼叫pushdown函式去更新之,滿足if(add[rt])就說明還沒有更新。

__int64 query(int l,int r,int 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

return res;

}

線段樹(區間修改 區間求和)lazy標記版

無lazy標記版 線段樹lazy標記模板題void push down int p,int lf,int rt 如果當前區間被標記但還要繼續遞迴下去,只需在下一次遞迴開始之前push down,向下標記子節點即可。區間修改 void update int p,int lf,int rt,int l,...

線段樹(區間查詢,區間修改) 標記永久化版

傳送門 為了不下傳add的標記,改為在詢問的過程當中計算每個遇到的節點對當前詢問的影響。而為了保證詢問的複雜度,子節點的影響需要在修改操作時計算好。因此實際上,add的值表示這個區間共同加上的值,seg表示這個區間內除了add之外其它數的值的和。需要注意,區間的add值可能有一部分在祖先節點上,這在...

線段樹 區間修改

我們對於線段樹的區間修改你可以用乙個最傻的辦法迴圈進行單點修改 時間複雜度太高十分麻瓜 所以,我們要用乙個聰明的做法延遲標記 lazy 我們在執行修改指令時,同樣可以在 l pl pr r 的情況下立即返回,只不過在回溯之前向節點p增加乙個標記,標識 該節點曾經被修改過,但其子節點尚未被更新 如果在...