简单数据结构——线段树

线段树常常用于求解某些区间上的问题,它通过区间标记和分治思想,可以较快的处理区间问题,在理解线段树前,我们先理解一种较为简单的思想——分块

分块:

  顾名思义,将要处理的区间分成块,一般一个块的大小为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

猜你喜欢

转载自www.cnblogs.com/by-w/p/9816274.html
今日推荐