線段樹簡介

 

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. }  

發佈了62 篇原創文章 · 獲贊 29 · 訪問量 77萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章