線段樹掃描線
火星探險(mars)
時間限制: 1000 ms 記憶體限制: 131072 KB【題目描述】
在2051年,若干火星探險隊探索了這顆紅色行星的不同區域並且製作了這些區域的地圖。現在,Baltic空間機構有一個雄心勃勃的計劃,他們想製作一張整個行星的地圖。為了考慮必要的工作,他們需要知道地圖上已經存在的全部區域的大小。你的任務是寫一個計算這個區域大小的程式。
具體任務要求為:
(1)從輸入中讀取地圖形狀的描述;
(2)計算地圖覆蓋的全部的區域;
(3)輸出探索區域的總面積(即所有矩形的公共面積)。
【輸入】
輸入的第一行包含一個整數n(1≤n≤10000),表示可得到的地圖數目。
以下n行,每行描述一張地圖。每行包含4個整數x1,y1,x2和y2(0≤x1<x2≤30000,0≤y1<y2≤30000)。數值(x1,y1)和(x2,y2)是座標,分別表示繪製區域的左下角和右上角座標。每張地圖是矩形的,並且它的邊是平行於x座標軸或y座標軸的。
【輸出】
輸出文件包含一個整數,表示探索區域的總面積(即所有矩形的公共面積)。
【輸入樣例】
2 10 10 20 20 15 15 25 30
【輸出樣例】
225
對於這種題我們的正解就是線段樹套掃描線,聽起來是不是逼格很高。
一、什麼是掃描線
什麼是掃描法?有什麼用?怎麼用?
可以想象成一根假想的線,將圖從左往右或從右往左或自下而上或自上而下“掃描”一遍,至於掃描的是什麼則根據具體應用選擇。
掃描線可以計算矩形面積、周長,可以計算線段交點,可以實現多邊形掃描轉換,在圖的處理方面經常用到。
這裡總結一下掃描線計算矩形面積的演算法。
怎麼用?首先,對於之前的圖,除了用總面積減去重合面積,還可以換一種計算方法,如圖:
此圖用4條橫線將整個圖劃分成了5個部分,顯然此時再算面積就可以用各個顏色的部分求和。
想想,這樣計算的整個慢過程:
假設我們的視線自下而上,首先,我們看到了最下面灰色矩形的下邊,
用這個下邊的長度乘以這條邊和上一條邊的高度差即得到灰色矩形面積,
繼續看到藍色的矩形的下邊,雖然藍色矩形有兩個,但我們計算時自然會用結合律將兩個矩形的下邊加起來再去乘以同樣的高,
然後重複這樣的操作,我們最終可以求得整個圖形的面積。
二、計算機實現
但是,這依舊是人做的,計算機要怎麼實現呢?
首先的問題是,計算機要怎麼儲存這張圖這些矩形?
從剛才的過程,我們不難發現,我們只需要儲存這張圖裡面的所有水平的邊即可。
對於每條邊,它所擁有的屬性是:這條邊的左右端點(的橫座標),這條邊的高度(縱座標),這條邊屬於矩形的上邊還是下邊(想想為什麼儲存這個屬性)
剛剛計算中我們遇到兩個藍色矩形的一部分一眼就能看出這兩個藍色矩形的‘寬’是多少,用計算機怎麼做到?
線段樹華麗登場!
我們以整個圖最左邊的豎線作為區間左端點,最右邊的豎線作為區間右端點,去維護這個區間的有效長度(即被覆蓋的長度)
比如掃到第2條邊的時候,有效長度就是兩個藍色矩形的寬之和。
這樣,我們用掃描線去掃描每一條邊的時候,都需要更新線段樹的有效長度
是如何更新的呢?
如果掃到的這條邊是某矩形的下邊,則往區間插入這條線段
如果掃到的這條邊是某矩形的上邊,則往區間刪除這條線段
為什麼?自己試著模擬一下就不難發現:
因為我們是自下而上的掃這個圖,掃到下邊相當於剛剛進入一個矩形,掃到上邊則是要離開一個矩形
利用線段樹把每條邊的有效長度找到了,也就是找到了每部分的所有矩形的總寬,那麼高呢?
高就簡單多了,對於所有的邊,按照高度從小到大排列,那麼矩形高就是每相鄰邊之間的高度差
三、手推一波
-
掃描線掃描的過程(建議配合程式碼模擬)
初始狀態
掃到最下邊的線, 點1→31→3更新為11
掃到第二根線, 此時S=lcnt!=0∗h兩根線之間S=lcnt!=0∗h兩根線之間, 得到綠色的面積, 加到答案中去, 隨後更新計數
同上, 將黃色的面積加到答案中去
同上, 將灰色的面積加到答案中去
同上, 將紫色的面積加到答案中去
同上, 將藍色的面積加到答案中去
四、程式碼實現
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 struct edge{ 5 int h,lx,rx,s; 6 bool operator < (const edge &b) const 7 { 8 return h<b.h; 9 } 10 }line[20005]; 11 struct sd{ 12 int son[2],l,r,len,s;//線段樹左右兒子,區間範圍,覆蓋線段層數 13 }t[200005];//ps.線段樹區間記錄的是豎線的編號 14 int X[20005];//記錄豎著的邊的x值 15 int cnt; 16 void build(int &v,int l1,int r1)//線段樹建樹 17 { 18 cnt++;v=cnt; 19 t[v].l=l1,t[v].r=r1; 20 if(l1==r1) return; 21 int mid=(l1+r1)>>1; 22 build(t[v].son[0],l1,mid); 23 build(t[v].son[1],mid+1,r1); 24 } 25 int find(int l1,int r1,int key)//二分查詢當前位置所屬線段編號,便於線上段樹中查詢 26 { 27 while(l1<=r1) 28 { 29 int mid=(l1+r1)/2; 30 if(X[mid]==key) return mid; 31 else 32 if(X[mid]<key) l1=mid+1; 33 else r1=mid-1; 34 } 35 } 36 void pushup(int v)//更新 37 { 38 if(t[v].s) 39 t[v].len=X[t[v].r+1]-X[t[v].l];//如果有線段覆蓋標記那就直接將標記賦值,不用下傳標記,因為每次查詢整個區間 40 else 41 if(t[v].l==t[v].r) 42 t[v].len=0; 43 else 44 t[v].len=t[t[v].son[0]].len+t[t[v].son[1]].len;//更新線段覆蓋長度 45 } 46 void update(int v,int l1,int r1,int c)//修改操作 47 { 48 if(t[v].l==l1&&t[v].r==r1) 49 { 50 t[v].s+=c;//區間覆蓋標記 51 pushup(v); 52 return; 53 } 54 int mid=(t[v].l+t[v].r)>>1; 55 if(l1>mid) update(t[v].son[1],l1,r1,c); 56 else 57 if(r1<=mid) update(t[v].son[0],l1,r1,c); 58 else 59 { 60 update(t[v].son[0],l1,mid,c); 61 update(t[v].son[1],mid+1,r1,c); 62 } 63 pushup(v); 64 } 65 int main() 66 { 67 int n; 68 scanf("%d",&n); 69 int tot=0; 70 for(int i=1;i<=n;i++) 71 { 72 int x1,x2,y1,y2; 73 scanf("%d%d%d%d",&x1,&y1,&x2,&y2); 74 tot++; 75 line[tot].lx=x1,line[tot].rx=x2,line[tot].h=y1,line[tot].s=1; 76 X[tot]=x1; 77 tot++; 78 line[tot].lx=x1,line[tot].rx=x2,line[tot].h=y2,line[tot].s=-1; 79 X[tot]=x2; 80 } 81 sort(X+1,X+1+tot); 82 sort(line+1,line+1+tot); 83 int root; 84 build(root,1,tot); 85 int k=0; 86 for(int i=1;i<=tot;i++)//離散化 87 { 88 if(X[i]!=X[i+1]) 89 { 90 k++,X[k]=X[i]; 91 } 92 } 93 long long res=0; 94 for(int i=1;i<=tot;i++) 95 { 96 int ll=find(1,k,line[i].lx);//找到當前區間左端點豎線編號 97 int rr=find(1,k,line[i].rx)-1;//當前區間右端點的豎線編號 98 update(1,ll,rr,line[i].s); 99 res+=(line[i+1].h-line[i].h)*t[1].len;//統計面積 100 } 101 printf("%lld",res); 102 return 0; 103 }
火星探險(mars)
時間限制: 1000 ms 記憶體限制: 131072 KB【題目描述】
在2051年,若干火星探險隊探索了這顆紅色行星的不同區域並且製作了這些區域的地圖。現在,Baltic空間機構有一個雄心勃勃的計劃,他們想製作一張整個行星的地圖。為了考慮必要的工作,他們需要知道地圖上已經存在的全部區域的大小。你的任務是寫一個計算這個區域大小的程式。
具體任務要求為:
(1)從輸入中讀取地圖形狀的描述;
(2)計算地圖覆蓋的全部的區域;
(3)輸出探索區域的總面積(即所有矩形的公共面積)。
【輸入】
輸入的第一行包含一個整數n(1≤n≤10000),表示可得到的地圖數目。
以下n行,每行描述一張地圖。每行包含4個整數x1,y1,x2和y2(0≤x1<x2≤30000,0≤y1<y2≤30000)。數值(x1,y1)和(x2,y2)是座標,分別表示繪製區域的左下角和右上角座標。每張地圖是矩形的,並且它的邊是平行於x座標軸或y座標軸的。
【輸出】
輸出文件包含一個整數,表示探索區域的總面積(即所有矩形的公共面積)。
【輸入樣例】
2 10 10 20 20 15 15 25 30
【輸出樣例】
225
對於這種題我們的正解就是線段樹套掃描線,聽起來是不是逼格很高。
一、什麼是掃描線
什麼是掃描法?有什麼用?怎麼用?
可以想象成一根假想的線,將圖從左往右或從右往左或自下而上或自上而下“掃描”一遍,至於掃描的是什麼則根據具體應用選擇。
掃描線可以計算矩形面積、周長,可以計算線段交點,可以實現多邊形掃描轉換,在圖的處理方面經常用到。
這裡總結一下掃描線計算矩形面積的演算法。
怎麼用?首先,對於之前的圖,除了用總面積減去重合面積,還可以換一種計算方法,如圖:
此圖用4條橫線將整個圖劃分成了5個部分,顯然此時再算面積就可以用各個顏色的部分求和。
想想,這樣計算的整個慢過程:
假設我們的視線自下而上,首先,我們看到了最下面灰色矩形的下邊,
用這個下邊的長度乘以這條邊和上一條邊的高度差即得到灰色矩形面積,
繼續看到藍色的矩形的下邊,雖然藍色矩形有兩個,但我們計算時自然會用結合律將兩個矩形的下邊加起來再去乘以同樣的高,
然後重複這樣的操作,我們最終可以求得整個圖形的面積。
二、計算機實現
但是,這依舊是人做的,計算機要怎麼實現呢?
首先的問題是,計算機要怎麼儲存這張圖這些矩形?
從剛才的過程,我們不難發現,我們只需要儲存這張圖裡面的所有水平的邊即可。
對於每條邊,它所擁有的屬性是:這條邊的左右端點(的橫座標),這條邊的高度(縱座標),這條邊屬於矩形的上邊還是下邊(想想為什麼儲存這個屬性)
剛剛計算中我們遇到兩個藍色矩形的一部分一眼就能看出這兩個藍色矩形的‘寬’是多少,用計算機怎麼做到?
線段樹華麗登場!
我們以整個圖最左邊的豎線作為區間左端點,最右邊的豎線作為區間右端點,去維護這個區間的有效長度(即被覆蓋的長度)
比如掃到第2條邊的時候,有效長度就是兩個藍色矩形的寬之和。
這樣,我們用掃描線去掃描每一條邊的時候,都需要更新線段樹的有效長度
是如何更新的呢?
如果掃到的這條邊是某矩形的下邊,則往區間插入這條線段
如果掃到的這條邊是某矩形的上邊,則往區間刪除這條線段
為什麼?自己試著模擬一下就不難發現:
因為我們是自下而上的掃這個圖,掃到下邊相當於剛剛進入一個矩形,掃到上邊則是要離開一個矩形
利用線段樹把每條邊的有效長度找到了,也就是找到了每部分的所有矩形的總寬,那麼高呢?
高就簡單多了,對於所有的邊,按照高度從小到大排列,那麼矩形高就是每相鄰邊之間的高度差
三、手推一波
-
掃描線掃描的過程(建議配合程式碼模擬)
初始狀態
掃到最下邊的線, 點1→31→3更新為11
掃到第二根線, 此時S=lcnt!=0∗h兩根線之間S=lcnt!=0∗h兩根線之間, 得到綠色的面積, 加到答案中去, 隨後更新計數
同上, 將黃色的面積加到答案中去
同上, 將灰色的面積加到答案中去
同上, 將紫色的面積加到答案中去
同上, 將藍色的面積加到答案中去
四、程式碼實現
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 struct edge{ 5 int h,lx,rx,s; 6 bool operator < (const edge &b) const 7 { 8 return h<b.h; 9 } 10 }line[20005]; 11 struct sd{ 12 int son[2],l,r,len,s;//線段樹左右兒子,區間範圍,覆蓋線段層數 13 }t[200005];//ps.線段樹區間記錄的是豎線的編號 14 int X[20005];//記錄豎著的邊的x值 15 int cnt; 16 void build(int &v,int l1,int r1)//線段樹建樹 17 { 18 cnt++;v=cnt; 19 t[v].l=l1,t[v].r=r1; 20 if(l1==r1) return; 21 int mid=(l1+r1)>>1; 22 build(t[v].son[0],l1,mid); 23 build(t[v].son[1],mid+1,r1); 24 } 25 int find(int l1,int r1,int key)//二分查詢當前位置所屬線段編號,便於線上段樹中查詢 26 { 27 while(l1<=r1) 28 { 29 int mid=(l1+r1)/2; 30 if(X[mid]==key) return mid; 31 else 32 if(X[mid]<key) l1=mid+1; 33 else r1=mid-1; 34 } 35 } 36 void pushup(int v)//更新 37 { 38 if(t[v].s) 39 t[v].len=X[t[v].r+1]-X[t[v].l];//如果有線段覆蓋標記那就直接將標記賦值,不用下傳標記,因為每次查詢整個區間 40 else 41 if(t[v].l==t[v].r) 42 t[v].len=0; 43 else 44 t[v].len=t[t[v].son[0]].len+t[t[v].son[1]].len;//更新線段覆蓋長度 45 } 46 void update(int v,int l1,int r1,int c)//修改操作 47 { 48 if(t[v].l==l1&&t[v].r==r1) 49 { 50 t[v].s+=c;//區間覆蓋標記 51 pushup(v); 52 return; 53 } 54 int mid=(t[v].l+t[v].r)>>1; 55 if(l1>mid) update(t[v].son[1],l1,r1,c); 56 else 57 if(r1<=mid) update(t[v].son[0],l1,r1,c); 58 else 59 { 60 update(t[v].son[0],l1,mid,c); 61 update(t[v].son[1],mid+1,r1,c); 62 } 63 pushup(v); 64 } 65 int main() 66 { 67 int n; 68 scanf("%d",&n); 69 int tot=0; 70 for(int i=1;i<=n;i++) 71 { 72 int x1,x2,y1,y2; 73 scanf("%d%d%d%d",&x1,&y1,&x2,&y2); 74 tot++; 75 line[tot].lx=x1,line[tot].rx=x2,line[tot].h=y1,line[tot].s=1; 76 X[tot]=x1; 77 tot++; 78 line[tot].lx=x1,line[tot].rx=x2,line[tot].h=y2,line[tot].s=-1; 79 X[tot]=x2; 80 } 81 sort(X+1,X+1+tot); 82 sort(line+1,line+1+tot); 83 int root; 84 build(root,1,tot); 85 int k=0; 86 for(int i=1;i<=tot;i++)//離散化 87 { 88 if(X[i]!=X[i+1]) 89 { 90 k++,X[k]=X[i]; 91 } 92 } 93 long long res=0; 94 for(int i=1;i<=tot;i++) 95 { 96 int ll=find(1,k,line[i].lx);//找到當前區間左端點豎線編號 97 int rr=find(1,k,line[i].rx)-1;//當前區間右端點的豎線編號 98 update(1,ll,rr,line[i].s); 99 res+=(line[i+1].h-line[i].h)*t[1].len;//統計面積 100 } 101 printf("%lld",res); 102 return 0; 103 }