1. 程式人生 > >簡單資料結構——線段樹

簡單資料結構——線段樹

線段樹常常用於求解某些區間上的問題,它通過區間標記和分治思想,可以較快的處理區間問題,在理解線段樹前,我們先理解一種較為簡單的思想——分塊

分塊:

  顧名思義,將要處理的區間分成塊,一般一個塊的大小為sqrt(n),

  例如,我們要對某個區間做加法,之後查詢一段的值,顯然我們對於每個塊用一個區間標記來表示這一個塊中的和, 

 以及一個區間標記表示這一段中的值都需要加上幾個值 ,

 修改操作的話,我們只需要對於區間標記做加法,若發現某一段不能完全的覆蓋一個塊的話,我們就在陣列中暴力修改

 因為至多隻有sqrt(n)個標記,暴力修改的時間也不會超過2*sqrt(n)所以一次的總時間是不會超過sqrt(n)

 而線段樹實際上是樹上的分塊,樹上的一個結點對應著一個區間資訊

 大致如下圖 

 

 一條線段就對應一個區間,同時對應樹上的一個點,

 一段需要修改的區間一定是線段樹上幾個連續的結點組成的

 那麼我們如何去找到這個結點呢?

 這裡就引入了線段樹的分治思想

對於區間[l,r]我們知道它一定被包含於區間【1,n】中

我們發現線段樹上左兒子的區間為【1,mid】,右兒子的區間為【mid+1,n】

首先我們要判斷這個【l,r】能否被左右兒子的區間完全包含,否則的話就將這段區間從mid開始拆開

原先的【l,r】就會被拆成【l,mid】和【mid+1,r】之後再分別求解

部分程式碼如下

procedure add(l,r,x,y,k,p:longint);
var mid:longint;
begin
  if (l=x) and (r=y) then
  begin
    tag[p]:=tag[p]+k;//修改區間標記
    exit;
  end;//判斷區間是否合法
  pushdown(l,r,x,y,k,p);//維護區間標記
  mid:=(l+r) div 2;
  if (y<=mid) then add(l,mid,x,y,k,p*2)//判斷區間是否完全被左兒子包含
  else 
  begin
    if (x>mid) then add(mid+1,r,x,y,k,p*2+1)//判斷區間是否完全被右兒子包含
    else
    begin//從mid開始將區間分成兩段
      add(l,mid,x,mid,k,p*2);
      add(mid+1,r,mid+1,y,k,p*2+1);
    end;
  end;
end;

問題又來了,我們找到對應的結點又怎麼辦呢?

一種高階大氣上檔次的方法出現了

lazy tag,延遲下放,類似於分塊的區間標記,但是這裡的區間標記是一層層的,非常不方便處理,

而延遲下放給了我們一條出路

我們不急於一次更新所有的結點,我們只需要在訪問到這個結點時將標記下放即可,

而線段樹的難點也在於如何實現,標記的下放與疊加

加法和乘法都非常簡單的進行下放和疊加

程式碼如下

//區間加法
var
tree,tag:array[0..1000000] of int64;
a:array[0..100000] of int64;
i:longint;
n,m,x,y,z,ch,ans:int64;
procedure pushdown(l,r,x,y,k,p:longint); //對樹上結點的更新
begin
  tree[p]:=tree[p]+(y-x+1)*k ;  //直接更當前結點(這種寫法不太好)
  tree[p]:=tree[p]+tag[p]*(r-l+1);//更新區間和
  tag[p*2]:=tag[p*2]+tag[p]; //標記疊加
  tag[p*2+1]:=tag[p*2+1]+tag[p];
  tag[p]:=0;//清空當前標記
end;
procedure add(l,r,x,y,k,p:longint);
var mid:longint;
begin
  //l,r是線段樹上對應的一段區間,x和y是需要查詢的一段區間
  if (l=x) and (r=y) then
  begin
    tag[p]:=tag[p]+k;
    exit;
  end;
  pushdown(l,r,x,y,k,p);
  mid:=(l+r) div 2;
  if (y<=mid) then add(l,mid,x,y,k,p*2)
  else 
  begin
    if (x>mid) then add(mid+1,r,x,y,k,p*2+1)
    else
    begin
      add(l,mid,x,mid,k,p*2);
      add(mid+1,r,mid+1,y,k,p*2+1);
    end;
  end;
end;
procedure sereach(l,r,x,y,p:longint);//查詢
var mid:longint;
begin
  pushdown(l,r,x,y,0,p);//更新訪問到的點
  if (l=x) and (r=y) then begin ans:=ans+tree[p]; exit; end;//發現結點匹配
  mid:=(l+r) div 2;
  //分治過程
  if (y<=mid) then sereach(l,mid,x,y,p*2)
  else
  begin
    if (x>mid) then sereach(mid+1,r,x,y,p*2+1)
    else
    begin
      sereach(l,mid,x,mid,p*2);
      sereach(mid+1,r,mid+1,y,p*2+1);
    end;
  end;
end;
begin
  readln(n,m);
  for i:=1 to n do
  begin
    read(a[i]);
    add(1,n,i,i,a[i],1);
  end;
  for i:=1 to m do
  begin
    read(ch);
    if ch=1 then begin read(x,y,z); add(1,n,x,y,z,1); end;
    if ch=2 then begin  ans:=0; read(x,y); sereach(1,n,x,y,1); writeln(ans); end; 
  end;
end.

 線段樹博大精深,這麼菜的我肯定是不會的啦QAQ