现在的位置: 首页 > 综合 > 正文

线段树简介

2013年10月22日 ⁄ 综合 ⁄ 共 8461字 ⁄ 字号 评论关闭
文章目录

 

6.1    线段树简介

线段树的定义如下:

        一棵二叉树,记为T (a,b),参数a,b表示该结点表示区间[a,b]。区间的长度b-a记为L。递归定义T[a,b]:

        若L>1 :[a, (a+b) div 2]为 T的左儿子

                        [(a+b)div 2,b]为T的右儿子。
        若L=1 :T为一个叶子结点。

表示区间[1, 10]的线段树表示如下:

树一般有两种形式:1、以点为结点。2、以线段为结点。区别如图:上面一个以线段为结点,下面一个以点为结点:


对线段树存在:

定理:线段树把区间上的任意一条线段都分成不超过2logL条线段。

这个结论为线段树能在O(logL)的时间内完成一条线段的插入、删除、查找等工作,提供了理论依据。

对线段树的可以进行扩展。

1.  测度。结点所表示区间中线段覆盖过的长度,存储在各结点中。

2.  独立线段数。区间中互不相交的线段条数。

3.  权和。区间所有元线段的权和。

测度的递推公式如下:

                 a[j] - a[i]                                                  该结点 Count>0

M =          0                                                              该结点为叶结点且 Count=0

                 Leftchild ↑ .M + Rightchild ↑ .M         该结点为内部结点且 Count=0连续段数

这里的连续段数指的是区间的并可以分解为多少个独立的区间。如 [1 , 2] ∪[2,3]∪ [5 , 6] 可以分解为两个区间[1 , 3] 与 [5 , 6] ,则连续段数为 2 。增加一个数据域 Lines_Tree.line 表示该结点的连续段数。 Line 的讨论比较复杂,内部结点不能简单地将左右孩子的 Line 相加。所以再增加 Lines_Tree.lbd 与 Lines_Tree.rbd 域。定义如下:  

                 1   左端点 I 被描述区间盖到

lbd  = 

                 0    左端点 I 不被描述区间盖到

 

                1     右端点 J 被描述区间盖到

rbd  = 

                0     右端点 J 不被描述区间盖到

 

lbd 与 rbd 的实现:

                1  该结点 count > 0

lbd  =      0  该结点是叶结点且 count = 0

                leftchild ↑ .lbd    该结点是内部结点且 Count=0

                1  该结点 count > 0

rbd  =    0  该结点是叶结点且 count = 0

                rightchild ↑ .rbd   该结点是内部结点且 Count=0

有了 lbd 与 rbd , Line 域就可以定义了:

                1  该结点 count > 0

Line =     0  该结点是叶结点且 count =0

                Leftchild ↑ .Line  +  Rightchild ↑.Line  -  1  当该结点是内部结点且 Count=0 , Leftchild ↑ .rbd = 1 且 Rightchild ↑ .lbd = 1

                Leftchild ↑.Line  +  Rightchild ↑ .Line   当该结点是内部结点且 Count=0 , Leftchild ↑ .rbd 与 Rightchild ↑ .lbd 不都为1

6.2    利用线段树实现区间的动态插入和删除

6.2.1   实例

PKU JudgeOnline, 1151, Atlantis.

6.2.2   问题描述

在二维平面分部着一些矩形,矩形有可能重合。求矩形的总面积。

6.2.3   分析

这个题在《算法艺术与信息学竞赛》中第一章介绍数据结构时,讲到线段树的时候有解题分析。

用线段树来记载纵向上是不是被覆盖,用测度来表示区间中被覆盖了多少长度。

为了降低复杂度,可以将坐标离散化,如下图所示:

从左到右扫描长方形的左侧边和右侧边,如果是左侧边则加入线段树中,否则从线段书中删除。同时用横向扫描的距离乘以线段树的测度,就得到了扫描过了的被覆盖的面积。

本题和PKU JudgeOnline,1117, Picture题都对线段树进行了扩展。本题只用到了测度的扩展,而1117题还用到了独立线段数的扩展。

6.2.4   程序

  1. //离散化+ 线段树+ 扫描线   
  2. //本题与JudgeOnline 1177 picture 极相似,现在回想起来甚至比1177 还要简单一些.与1177 不同的是本题中的坐标是浮点
      
  3. //类型的故不能将坐标直接离散.我们必须为它们建立一个对应关系,用一个整数去对应一个浮点数
      
  4. //这样的对应关系在本题的数组y[] 中   
  5. #include<iostream>
      
  6. #include<algorithm>   
  7. #include<cmath>   
  8. #include<iomanip>   
  9. using namespace std;  
  10.    
  11. struct node{  
  12.      int st, ed,c;   //c : 区间被覆盖的层数,m: 区间的测度
      
  13.      double m;  
  14. }ST[802];  
  15. struct line{  
  16.      doublex,y1,y2;   //纵方向直线, x:直线横坐标, y1 y2:直线上的下面与上面的两个纵坐标
      
  17.      bools;     //s = 1: 直线为矩形的左边, s = 0:直线为矩形的右边
      
  18. }Line[205];  
  19. double y[205],ty[205]; //y[] 整数与浮点数的对应数组;ty[]:用来求y[]的辅助数组
      
  20.    
  21. void build(int root, int st, int ed){  
  22.      ST[root].st = st;  
  23.      ST[root].ed = ed;  
  24.      ST[root].c = 0;  
  25.      ST[root].m = 0;  
  26.      if(ed - st> 1){  
  27.          int mid= (st+ed)/2;  
  28.          build(root*2, st, mid);  
  29.          build(root*2+1, mid, ed);  
  30.      }  
  31. }  
  32. inline void updata(int root){  
  33.      if(ST[root].c> 0)  
  34.          //将线段树上区间的端点分别映射到y[]数组所对应的浮点数上,由此计算出测度
      
  35.          ST[root].m = y[ST[root].ed-1] -y[ST[root].st-1];  
  36.      else if(ST[root].ed - ST[root].st == 1)  
  37.          ST[root].m = 0;  
  38.      elseST[root].m = ST[root*2].m + ST[root*2+1].m;  
  39. }  
  40. void insert(int root, int st, int ed){  
  41.      if(st <=ST[root].st && ST[root].ed <= ed){  
  42.          ST[root].c++;  
  43.          updata(root);  
  44.          return;  
  45.      }  
  46.      if(ST[root].ed- ST[root].st == 1)return ;//不出错的话这句话就是冗余的
      
  47.      int mid =(ST[root].ed + ST[root].st)/2;  
  48.      if(st <mid)  
  49.          insert(root*2, st, ed);  
  50.      if(ed >mid)  
  51.          insert(root*2+1, st, ed);  
  52.      updata(root);  
  53. }  
  54. void Delete(int root, int st, int ed){  
  55.      if(st <=ST[root].st && ST[root].ed <= ed){  
  56.          ST[root].c--;  
  57.           updata(root);  
  58.          return;  
  59.      }  
  60.      if(ST[root].ed- ST[root].st == 1)return ; //不出错的话这句话就是冗余的
      
  61.      int mid =(ST[root].st + ST[root].ed)/2;  
  62.      if(st <mid)  
  63.          Delete(root*2, st, ed);  
  64.      if(ed >mid)  
  65.          Delete(root*2+1, st, ed);  
  66.      updata(root);  
  67. }  
  68. int Correspond(int n, double t){  
  69.      //二分查找出浮点数t 在数组y[]中的位置(此即所谓的映射关系)
      
  70.      intlow,high,mid;  
  71.      low = 0; high = n-1;  
  72.      while(low< high){  
  73.          mid = (low+high)/2;  
  74.          if(t> y[mid])  
  75.               low = mid + 1;  
  76.          elsehigh = mid;  
  77.      }  
  78.      returnhigh+1;  
  79. }  
  80. bool cmp(line l1, line l2){  
  81.      return l1.x< l2.x;  
  82. }  
  83.    
  84. int main()  
  85. {  
  86.      intn,i,num,l,r,c=0;  
  87.      doublearea,x1,x2,y1,y2;  
  88.      while(cin>>n,n){  
  89.          for(i =0; i < n; i++){  
  90.               cin>>x1>>y1>>x2>>y2;  
  91.               Line[2*i].x = x1; Line[2*i].y1 =y1; Line[2*i].y2 = y2; Line[2*i].s = 1;  
  92.               Line[2*i+1].x = x2; Line[2*i+1].y1= y1; Line[2*i+1].y2 = y2; Line[2*i+1].s = 0;  
  93.               ty[2*i] = y1; ty[2*i+1] = y2;  
  94.          }  
  95.          n <<= 1;  
  96.          sort(Line, Line+n, cmp);  
  97.          sort(ty, ty+n);  
  98.          y[0] = ty[0];  
  99.          //处理数组ty[]使之不含重覆元素,得到新的数组存放到数组y[]中
      
  100.          for(i=num=1;i < n; i++)  
  101.               if(ty[i]!= ty[i-1])  
  102.                    y[num++] = ty[i];  
  103.          build(1, 1, num); //树的叶子结点与数组y[]中的元素个数相同,以便建立一一对应的关系
      
  104.          area = 0;  
  105.          for(i =0; i < n-1; i++){  
  106.               //由对应关系计算出线段两端在树中的位置
      
  107.               l = Correspond(num, Line[i].y1);  
  108.               r = Correspond(num, Line[i].y2);  
  109.               if(Line[i].s)//插入矩形的左边
      
  110.                    insert(1, l, r);  
  111.               else    //删除矩形的右边
      
  112.                    Delete(1, l, r);  
  113.               area += ST[1].m * (Line[i+1].x -Line[i].x);  
  114.          }  
  115.          cout<<"Testcase #"<<++c<<endl<<"Totalexplored area: ";  
  116.          cout<<fixed<<setprecision(2)<<area<<endl<<endl;  
  117.      }  
  118.      return 0;  
  119. }  

6.3    计算数组区间第K大的数

PKU JudgeOnline, 2761, Feed the dogs则是线段树的另外一个应用:实用线段树来计算数组区间[i, j]中元素第k小(或第K大)的数。只要添写一个函数,根据线段树中每个结点的覆盖树木来判断第k大的树是哪一个。

当初始化,或者区间[i, j]发生变化时,需要对线段树进行添加或者删除操作。每当增加(或删除)一个大小为X的点时,就在树上添加(或删除)一条(X,MaxLen)的线段(不含端点),当要查询一个点的排名时,只要看看其上有多少条线段就可以了。

  1. int query(int root, intcount)  
  2. {  
  3.      if(count<= ST[root].c){  
  4.          returnST[root].st;  
  5.      }else if(ST[root].ed - ST[root].st == 1){  
  6.          returnST[root].ed;  
  7.      }  
  8.      count -= ST[root].c;  
  9.      if(count<= ST[root*2+1].c){  
  10.          returnquery(root*2, count);  
  11.      }else{  
  12.          returnquery(root*2+1, count);  
  13.      }  
  14. }  

【上篇】
【下篇】

抱歉!评论已关闭.