线段树常常用于求解某些区间上的问题,它通过区间标记和分治思想,可以较快的处理区间问题,在理解线段树前,我们先理解一种较为简单的思想——分块
分块:
顾名思义,将要处理的区间分成块,一般一个块的大小为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