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 程序
- //離散化+ 線段樹+ 掃描線
- //本題與JudgeOnline 1177 picture 極相似,現在回想起來甚至比1177 還要簡單一些.與1177 不同的是本題中的座標是浮點
- //類型的故不能將座標直接離散.我們必須爲它們建立一個對應關係,用一個整數去對應一個浮點數
- //這樣的對應關係在本題的數組y[] 中
- #include<iostream>
- #include<algorithm>
- #include<cmath>
- #include<iomanip>
- using namespace std;
- struct node{
- int st, ed,c; //c : 區間被覆蓋的層數,m: 區間的測度
- double m;
- }ST[802];
- struct line{
- doublex,y1,y2; //縱方向直線, x:直線橫座標, y1 y2:直線上的下面與上面的兩個縱座標
- bools; //s = 1: 直線爲矩形的左邊, s = 0:直線爲矩形的右邊
- }Line[205];
- double y[205],ty[205]; //y[] 整數與浮點數的對應數組;ty[]:用來求y[]的輔助數組
- void build(int root, int st, int ed){
- ST[root].st = st;
- ST[root].ed = ed;
- ST[root].c = 0;
- ST[root].m = 0;
- if(ed - st> 1){
- int mid= (st+ed)/2;
- build(root*2, st, mid);
- build(root*2+1, mid, ed);
- }
- }
- inline void updata(int root){
- if(ST[root].c> 0)
- //將線段樹上區間的端點分別映射到y[]數組所對應的浮點數上,由此計算出測度
- ST[root].m = y[ST[root].ed-1] -y[ST[root].st-1];
- else if(ST[root].ed - ST[root].st == 1)
- ST[root].m = 0;
- elseST[root].m = ST[root*2].m + ST[root*2+1].m;
- }
- void insert(int root, int st, int ed){
- if(st <=ST[root].st && ST[root].ed <= ed){
- ST[root].c++;
- updata(root);
- return;
- }
- if(ST[root].ed- ST[root].st == 1)return ;//不出錯的話這句話就是冗餘的
- int mid =(ST[root].ed + ST[root].st)/2;
- if(st <mid)
- insert(root*2, st, ed);
- if(ed >mid)
- insert(root*2+1, st, ed);
- updata(root);
- }
- void Delete(int root, int st, int ed){
- if(st <=ST[root].st && ST[root].ed <= ed){
- ST[root].c--;
- updata(root);
- return;
- }
- if(ST[root].ed- ST[root].st == 1)return ; //不出錯的話這句話就是冗餘的
- int mid =(ST[root].st + ST[root].ed)/2;
- if(st <mid)
- Delete(root*2, st, ed);
- if(ed >mid)
- Delete(root*2+1, st, ed);
- updata(root);
- }
- int Correspond(int n, double t){
- //二分查找出浮點數t 在數組y[]中的位置(此即所謂的映射關係)
- intlow,high,mid;
- low = 0; high = n-1;
- while(low< high){
- mid = (low+high)/2;
- if(t> y[mid])
- low = mid + 1;
- elsehigh = mid;
- }
- returnhigh+1;
- }
- bool cmp(line l1, line l2){
- return l1.x< l2.x;
- }
- int main()
- {
- intn,i,num,l,r,c=0;
- doublearea,x1,x2,y1,y2;
- while(cin>>n,n){
- for(i =0; i < n; i++){
- cin>>x1>>y1>>x2>>y2;
- Line[2*i].x = x1; Line[2*i].y1 =y1; Line[2*i].y2 = y2; Line[2*i].s = 1;
- Line[2*i+1].x = x2; Line[2*i+1].y1= y1; Line[2*i+1].y2 = y2; Line[2*i+1].s = 0;
- ty[2*i] = y1; ty[2*i+1] = y2;
- }
- n <<= 1;
- sort(Line, Line+n, cmp);
- sort(ty, ty+n);
- y[0] = ty[0];
- //處理數組ty[]使之不含重覆元素,得到新的數組存放到數組y[]中
- for(i=num=1;i < n; i++)
- if(ty[i]!= ty[i-1])
- y[num++] = ty[i];
- build(1, 1, num); //樹的葉子結點與數組y[]中的元素個數相同,以便建立一一對應的關係
- area = 0;
- for(i =0; i < n-1; i++){
- //由對應關係計算出線段兩端在樹中的位置
- l = Correspond(num, Line[i].y1);
- r = Correspond(num, Line[i].y2);
- if(Line[i].s)//插入矩形的左邊
- insert(1, l, r);
- else //刪除矩形的右邊
- Delete(1, l, r);
- area += ST[1].m * (Line[i+1].x -Line[i].x);
- }
- cout<<"Testcase #"<<++c<<endl<<"Totalexplored area: ";
- cout<<fixed<<setprecision(2)<<area<<endl<<endl;
- }
- return 0;
- }
6.3 計算數組區間第K大的數
PKU JudgeOnline, 2761, Feed the dogs則是線段樹的另外一個應用:實用線段樹來計算數組區間[i, j]中元素第k小(或第K大)的數。只要添寫一個函數,根據線段樹中每個結點的覆蓋樹木來判斷第k大的樹是哪一個。
當初始化,或者區間[i, j]發生變化時,需要對線段樹進行添加或者刪除操作。每當增加(或刪除)一個大小爲X的點時,就在樹上添加(或刪除)一條(X,MaxLen)的線段(不含端點),當要查詢一個點的排名時,只要看看其上有多少條線段就可以了。
- int query(int root, intcount)
- {
- if(count<= ST[root].c){
- returnST[root].st;
- }else if(ST[root].ed - ST[root].st == 1){
- returnST[root].ed;
- }
- count -= ST[root].c;
- if(count<= ST[root*2+1].c){
- returnquery(root*2, count);
- }else{
- returnquery(root*2+1, count);
- }
- }