題目
「VOJ1056」圖形面積
桌面上放了個平行於座標軸的矩形,這個矩形可能有互相覆蓋的部分,求它們組成的圖形的面積。
【輸入】
有多組測試數據。
每組測試數據輸入第一行爲一個數,表示矩形的數量。
下面行,每行四個整數,分別表示每個矩形的左下角和右上角的座標,座標範圍爲到之間的整數。
當時,測試文件結束。
【輸出】
對於每個測試用例,在單獨一行中輸出一個整數,表示圖形的面積。
【樣例輸入】
1
1 2 3 4
3
1 1 4 3
2 -1 3 2
4 0 5 2
0
【樣例輸出】
4
10
【數據範圍】
對於的數據,,每個測試點測試數據組數。
對於的數據,座標範圍爲到之間的整數。
對於的數據,,每個測試點測試數據組數,座標範圍爲到之間的整數。
線段樹掃描線
線段樹掃描線就是用來解決圖像面積並的問題。
例如存在下面三個矩形:
我們可以根據矩形的左右邊界分成多個部分求解:
這些紅色的線就稱爲掃描線。
掃描線
從左往右掃過矩形左右邊,每兩條掃描線之間的面積總和就是圖形面積。
每一段面積 = (覆蓋掃描線上的長度和)*(與下一條掃描線的距離)。
可以發現,影響掃描線上的覆蓋長度,只與矩形左右邊相關,因此可以將圖形簡化:
構造一個四元組,即結構體,來存在這些矩形左右邊。
:邊的橫座標;
:邊的兩個端點,長度爲yy-y;
:表示矩形的左邊界,表示矩形的右邊界。
在掃描線上的覆蓋長度,可以看作邊在軸上的投影,我們將投影分成多段區間:
每一條邊的投影包含連續的多段區間,求掃描線的覆蓋總長度,其實就是多段區間的覆蓋長度。
如果將每段區間看作序列中的一個點,就相當於線段樹的區間修改、區間求和。
(1)構造一個線段樹維護的序列,每個點對應一個區間。
因爲縱座標有重複,需要去重排序。設去重排序後的序列爲,有個。分成段區間。
那麼第段區間的左端點爲,區間長度爲。
再添加兩個數組:
:表示線段樹結點表示區間的覆蓋次數。
:表示線段樹結點表示覆蓋區間的長度。
(2)對於每一條邊,相當於對覆蓋的區間結點進行增加。
如何找到和對應哪一段區間?
二分查找,因爲已經是去重且有序的。
需要注意邊界情況:
,,表示第段區間,區間爲。爲二分函數。
同時根據覆蓋次數更新結點:
當覆蓋次數爲時,結點代表的區間不參與計算。
if(cnt[k]!=0) len[k]=dy[r+1]-dy[l]; //進行了覆蓋,區間長度就是第l段~第r段區間的長度
else len[k]=len[2*k]+len[2*k+1]; //沒有覆蓋,區間長度就是左右孩子區間長度之和
(3)區間求和,求投影到軸的覆蓋區間。
對於兩條邊
掃描線之間的寬度就是
掃描線上的覆蓋長度是
兩條掃描線之間的面積:
,就是最後的答案。
參考程序
#include<bits/stdc++.h>
#define N 2010
#define LL long long
using namespace std;
struct Line
{
int x,y,yy,flag; //掃面線,橫座標爲x,長度爲y~yy
}line[2*N];
struct Tree
{
LL len;
int cnt;
}tree[10*N];//cnt統計區間覆蓋次數,len統計覆蓋的區間長度
int n;
int temp[2*N],dy[2*N],t;
void dist() //縱座標去重排序
{
sort(temp+1,temp+n+1);
dy[++t]=temp[1];
for(int i=2;i<=n;i++)
if(temp[i]!=temp[i-1]) dy[++t]=temp[i];
t--; //有t個點,分成t-1個段區間
return;
}
int comp(Line a,Line b)
{
return a.x<b.x?1:0;
}
void built(int k,int l,int r) //建樹,初始化,第k個結點表示區間[dy[l],dy[l+1]]
{
tree[k].len=0;
tree[k].cnt=0;
if(l==r) return;
int mid=(l+r)/2;
built(2*k,l,mid);
built(2*k+1,mid+1,r);
}
int Half(int k) //二分查找縱座標對應那一段區間
{
int l=1,r=t+1;
while(l<=r)
{
int mid=(l+r)/2;
if(dy[mid]==k) return mid;
if(dy[mid]<k) l=mid+1;
else r=mid-1;
}
}
void pushdown(int k,int l,int r)
{
if(tree[k].cnt>0) tree[k].len=dy[r+1]-dy[l]; //覆蓋的區間長度
else tree[k].len=tree[2*k].len+tree[2*k+1].len;
}
void update(int k,int l,int r,int X,int Y,int T)
{
if(l>Y||r<X) return; //第l~r段區間,是[dy[l],dy[r+1])
if(X<=l&&r<=Y)
{
tree[k].cnt+=T;
pushdown(k,l,r);
return;
}
int mid=(l+r)/2;
update(2*k,l,mid,X,Y,T);
update(2*k+1,mid+1,r,X,Y,T);
pushdown(k,l,r); //回溯更新len
}
int main()
{
while((scanf("%d",&n))&&n)
{
LL ans=0;
t=0;
int a,b,c,d;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
line[2*i-1]=Line{a,b,d,1}; //矩形左邊界
line[2*i]=Line{c,b,d,-1}; //矩形右邊界
temp[2*i-1]=b; //縱座標
temp[2*i]=d;
}
n*=2;
dist(); //縱座標去重排序
built(1,1,t);
sort(line+1,line+n+1,comp); //掃描線排序
for(int i=1;i<=n;i++)
{
if(i>1&&line[i].x!=line[i-1].x) ans+=(line[i].x-line[i-1].x)*tree[1].len;
int X=Half(line[i].y),Y=Half(line[i].yy)-1;
update(1,1,t,X,Y,line[i].flag);
}
printf("%lld\n",ans);
}
return 0;
}