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