簡單資料結構——線段樹
線段樹常常用於求解某些區間上的問題,它通過區間標記和分治思想,可以較快的處理區間問題,在理解線段樹前,我們先理解一種較為簡單的思想——分塊
分塊:
顧名思義,將要處理的區間分成塊,一般一個塊的大小為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