ACM-ICPC必备之 线段树

http://www.dzkjzz.com/ShowArticle.asp?id=105

处理涉及到图形的面积、周长等问题的时候,并不需要依赖很深的数学知识,但要提高处理此类问题的效率却又十分困难。这就需要从根本上改变算法的基础——数据结构。这里要说的就是一种特殊的数据结构——线段树。

我们往往遇到这样的问题,研究的对象是数量庞大的线段,普通的方法往往是O(n^2),的,而用线段树我们可以做到O(nlogn)。

线段树是一棵完全二叉树,它的根节点代表一个区间[a,b],左结点是[a,(a+b) div 2],右结点是[(a+b) div 2,b],且左右结点分别是线段树,注意我们这里说的是线段,[a,b]即表示数轴上a,b两点间的线段,所以左结点和右结点并没有交。

线段数的基本操作有插入、查找、删除,而插入删除的操作都是建立在查找的基础上的:

查找线段[a,b]

根据当前结点的边界中点把[a,b]分成最多2部分,然后递归查找它的左右结点。

删除[a,b]

找到[a,b]后,将其结点的相关数据清除即可,不需要删除结点。

插入[a,b]

找到[a,b]后,添加其结点的相关数据。

相关数据依题目的要求不同而不同,这里不一一列举。

因为线段树是一棵完全二叉树,所以可以用数组模拟,父结点s,左孩子s*2,右孩子s*2+1。

线段树的复杂度就是查找的复杂度,不难证明每层最多只访问2个结点,而总层数是log(b-a),所以复杂度是O(2log(b-a))=O(logn)


 


 

【摘要】

本文通过对线段树程序实现的介绍,和具体应用题目的分析,使读者对线段树有一个初步的理解,并能在做类似题目或者软件编写上,更加从容,起到扩展知识面的作用。

 

【关键字】

线段树 时间复杂度 树状数组 优化

 

【正文目录】

 

 

第一章   线段树基础知识

1.1线段树的定义

1.2线段树的性质与时空复杂度简介

1.3线段树结点的基本存储类型

1.4线段树的常用操作与及实现方法

 

第二章   线段树的两种变化

2.1线段树的第一种变化

2.2线段树的第二种变化 (树状数组)

 

第三章   线段树的一些优化

3.1线段树的简单优化

3.2线段树的扩展

 

【正文】

 

在学习与竞赛中,经常遇到与区间、图形的面积、周长等有关的问题。处理涉及这些知识的问题,并不需要依赖很深的数学知识,但要提高处理此类问题的效率简单的结构又很难做到,这里引入一种特殊的数据结构:线段树。一条线段又是对应于一个区间而言的,因此线段树也叫区间树。

 

第一章 线段树基础知识

 

1.1    线段树的定义

 

定义1 长度为1的线段称为元线段。

 

定义2 一棵树被成为线段树,当且仅当这棵树满足如下条件:

(1)    该树是一棵二叉树。

(2)    树中每一个结点都对应一条线段[a,b]

(3)    树中结点是叶子结点当且仅当它所代表的线段是元线段。

(4)    树中非叶子结点都有左右两个子树,做子树树根对应线段[a , (a + b ) / 2],右子树树根对应线段[( a + b ) / 2 , b]

 

 

下图就是一棵长度范围为[1 , 10]的线段树。


但是这种二叉树较为平衡,和静态二叉树一样,提前根据应用的部分建立好树形结构。针对性强,所以效率要高。一般来说,动态结构较为灵活,但是速度较慢;静态结构节省内存,速度较快。

 

1.2    线段树的性质与时空复杂度简介

 

下面介绍线段树的两个性质(证明略)。

性质1 长度范围为[1,L]的一棵线段树的深度不超过log(L-1) + 1

性质2 线段树把区间上的任意一条长度为L的线段都分成不超过2logL条线段。

 

空间复杂度 存储一棵线段树的空间复杂度一般为OL)。

 

时间复杂度 对于插入线段、删除线段,查找元素,查找区间最值等操作,复杂度一般都是Olog L)。

线段树主要应用了平衡与分治的性质,所以基本时间复杂度都和log有关。我们在应用线段树解决问题的时候,应尽量在构造好线段树的时候,使每种操作在同一层面上操作的次数为O1),这样能够维持整体的复杂度Olog L)。

 

1.3    线段树结点的基本存储类型

 

基本的数据类型结构类:

class Node {

Node * leftChild, * rightChild;

int leftSide, rightSide;

Element element;

};

 

其中leftChildrightChild是分别指向左右儿子的指针,当该结点为叶子结点的时候,二者值为NULL

leftSiderightSide表示此结点对应线段为[leftSide,rightSide]

在程序实现的时候也可以不将这两者记录下来,而采取参数传递的办法得到等价的信息。

Element即是存储的除结构外的信息,来实现程序的目的。

 

1.4    线段树的常用操作及实现方法

 

我们来看一个例题,通过例题来了解线段树的操作与及实现方法。

 

例题1ZJU1610 Count The Colors 线段树基本应用题目)

 

题目原文:http://acm.zju.edu.cn/show_problem.php?pid=1610

 

简要翻译:给出在线段[0,8000]上的若干次涂色,问最后能看见哪些颜色,并统计能看到多少段。

 

解析:

就这个题目而言,方法很多,而且数据范围不大,但我们从线段树的角度来解决这个问题。

建树自然就是建立一棵代表线段[0,8000]的线段树,涂色操作就是将[a , b]涂成颜色c。最后做统计。

所以结构中Element可以改为Color

Color>=0当且仅当改结点代表的线段所有颜色均为Color

那么每一个球的颜色可以表示成深度最小的包含此球的Color>=0线段的颜色。

于是我们可以从前到后模拟图色过程,最后做一个统计即可。于是复杂度为On log L),其中n为操作数,L为线段长度。

 

线段树的另外一个应用就是查询线段有否被一些线段覆盖,并随时查询当前被覆盖线段的总长度。那么此时可以在结点结构中加入一个整型变量count;代表当前结点代表的子树中被覆盖的线段长度和。这样就要在插入(删除)当中维护这个count值,于是当前的覆盖总值就是根节点的count值了。

 

//解题报告!!!!!

思路

       线段树.

参考程序

var
    tree:array[1..30000] of longint;
    sum:array[0..8000] of longint;
    n,i,colour,x1,x2:longint;

//杂色是 -1

procedure pyq(p:longint);//分割线段函数,把自己的颜色给 孩子,杂色的不用分割
    begin                           //这道题的关键!!!,解决线段被覆盖的问题!!
      if tree[p]<>-1 then
        begin
          tree[p*2]:=tree[p];
          tree[p*2+1]:=tree[p];
          tree[p]:=-1;
        end;
    end;

procedure insert(p,left,right,a,b:longint);
    var
      mid:longint;
    begin
      mid:=(left+right) div 2;
      if (left=a)and(right=b) then tree[p]:=colour
      else if mid>=b then
        begin
          pyq(p);
          insert(p*2,left,mid,a,b);
        end
      else if a>=mid then
        begin
          pyq(p);
          insert(p*2+1,mid,right,a,b);
        end
      else
        begin
          pyq(p);
          insert(p*2,left,mid,a,mid);
          insert(p*2+1,mid,right,mid,b);
        end;
    end;

procedure count(p,left,right:longint);
    var
      mid:longint;
    begin
      mid:=(left+right) div 2;
      if (tree[p]=-2) then
        begin
          colour:=-2;
          exit;
        end
      else if (colour<>tree[p])and(tree[p]<>-1) then
        begin
          colour:=tree[p];
          inc(sum[colour]);
        end
      else if (right-left=1)or(colour=tree[p]) then exit
      else if tree[p]=-1 then
        begin
          count(p*2,left,mid);
          count(p*2+1,mid,right);
        end;
    end;

procedure print;
    var
      i:longint;
    begin
      for i:=0 to 8000 do
        if sum[i]<>0 then
          writeln(i,' ',sum[i]);
      writeln;
    end;

begin
    while not eof do
      begin
        readln(n);
        fillchar(sum,sizeof(sum),0);
        for i:=1 to 30000 do
          tree[i]:=-2;
        for i:=1 to n do
          begin
            readln(x1,x2,colour);
            insert(1,0,8000,x1,x2);
          end;
        colour:=-3;
        count(1,0,8000);
        print;
      end;
end.

 

你可能感兴趣的:(数据结构)