標記永久化

1.概述
在可持久化線段樹中,我們常常要使用區間修改操作。這時候,如果再用下傳標記再向上更新的方式來實現就會變得十分麻煩。

那麼,有沒有一種實現線段樹區間修改的方式可以不用下傳標記或向上更新呢?有,那就是標記永久化。
2.原理
標記永久化的原理簡單來說就是修改時一路更改被影響到的點,詢問時則一路累加路上的標記,從而省去下傳標記的操作。
3.代碼實現
3.0 說明
這裏以區間修改區間求和的線段樹爲例。
線段樹中編號爲p的結點的值和標記分別爲val[p]和mark[p]。
3.1 建樹
標記永久化線段樹的建樹和標記不永久化線段樹的建樹沒有什麼區別,這裏就不在贅述,直接上代碼吧。

procedure build(p,l,r:longint);
var
        mid:longint;
begin
        if (l=r) then
        begin
                read(val[p]);
                exit;
        end;
        mid:=(l+r)>>1;
        build(p<<1,l,mid);
        build(p<<1+1,mid+1,r);
        val[p]:=val[p<<1]+val[p<<1+1];
end;

3.2 區間修改
0.設要將區間[x,y]中的數都加上v。
1.一路走下去同時更新路上受此次修改影響的節點的值,即inc(val[p],(y-x+1)*v)。
2.當目前結點所代表的區間與待修改區間完全重合時,更新標記,返回,即inc(mark[p],v);

procedure odd(p,l,r,x,y:longint;v:int64);
var
        mid:longint;
begin
        inc(val[p],(y-x+1)*v);
        if (l=x) and (r=y) then
        begin
                inc(mark[p],v);
                exit;
        end;
        mid:=(l+r)>>1;
        if (y<=mid) then odd(p<<1,l,mid,x,y,v)
        else if (x>mid) then odd((p<<1) or 1,mid+1,r,x,y,v)
        else
        begin
                odd(p<<1,l,mid,x,mid,v);
                odd((p<<1) or 1,mid+1,r,mid+1,y,v);
        end;
end;

有人可能會問:標記更新後直接返回的話下面的結點不就沒更新了嗎?
慢慢來嘛,往下看就明白了。

3.3 區間查詢
0.設求區間[x…y]中數的總和
1.一路走下去同時累加路上的標記,因爲在修改操作中標記並沒有下傳,所以要這樣子,即inc(ad,mark[p])。
2.當目前結點所代表的區間與待修改區間完全重合時,返回當前結點的值與累加下來的標記乘上詢問區間長度的和,即exit(val[p]+(y-x+1)*ad)。

function ask(p,l,r,x,y:longint;ad:int64):int64;
var
        mid:longint;
begin
        if (l=x) and (r=y) then exit(val[p]+(y-x+1)*ad);
        mid:=(l+r)>>1;
        if (y<=mid) then exit(ask((p<<1),l,mid,x,y,ad+mark[p]));
        if (x>mid) then exit(ask((p<<1) or 1,mid+1,r,x,y,ad+mark[p]));
        exit(ask(p<<1,l,mid,x,mid,ad+mark[p])+ask(p<<1 or 1,mid+1,r,mid+1,y,ad+mark[p]));
end;

4.練習題
luogu P3372

var
        val,mark:array[0..400005] of int64;
        n,m,i,opt,k,x,y:longint;
procedure build(p,l,r:longint);
var
        mid:longint;
begin
        if (l=r) then
        begin
                read(val[p]);
                exit;
        end;
        mid:=(l+r)>>1;
        build(p<<1,l,mid);
        build(p<<1+1,mid+1,r);
        val[p]:=val[p<<1]+val[p<<1+1];
end;

procedure odd(p,l,r,x,y:longint;v:int64);
var
        mid:longint;
begin
        inc(val[p],(y-x+1)*v);
        if (l=x) and (r=y) then
        begin
                inc(mark[p],v);
                exit;
        end;
        mid:=(l+r)>>1;
        if (y<=mid) then odd(p<<1,l,mid,x,y,v)
        else if (x>mid) then odd((p<<1) or 1,mid+1,r,x,y,v)
        else
        begin
                odd(p<<1,l,mid,x,mid,v);
                odd((p<<1) or 1,mid+1,r,mid+1,y,v);
        end;
end;

function ask(p,l,r,x,y:longint;ad:int64):int64;
var
        mid:longint;
begin
        if (l=x) and (r=y) then exit(val[p]+(y-x+1)*ad);
        mid:=(l+r)>>1;
        if (y<=mid) then exit(ask((p<<1),l,mid,x,y,ad+mark[p]));
        if (x>mid) then exit(ask((p<<1) or 1,mid+1,r,x,y,ad+mark[p]));
        exit(ask(p<<1,l,mid,x,mid,ad+mark[p])+ask(p<<1 or 1,mid+1,r,mid+1,y,ad+mark[p]));
end;

begin
        readln(n,m);
        build(1,1,n);
        for i:=1 to m do
        begin
                read(opt,x,y);
                if (opt=1) then
                begin
                        read(k);
                        odd(1,1,n,x,y,k);
                end
                else
                        writeln(ask(1,1,n,x,y,0));
        end;
end.

5.標記永久化線段樹和標記不永久化線段樹的差異

標記永久化線段樹用時:
在這裏插入圖片描述
標記不永久化線段樹用時:在這裏插入圖片描述
明顯快了很多。
並且碼量也少了很多。

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