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.標記永久化線段樹和標記不永久化線段樹的差異
標記永久化線段樹用時:
標記不永久化線段樹用時:
明顯快了很多。
並且碼量也少了很多。