【詳解】線段樹掃描線

題目

「VOJ1056」圖形面積

桌面上放了NN個平行於座標軸的矩形,這NN個矩形可能有互相覆蓋的部分,求它們組成的圖形的面積。
【輸入】
有多組測試數據。
每組測試數據輸入第一行爲一個數NN,表示矩形的數量。
下面NN行,每行四個整數,分別表示每個矩形的左下角和右上角的座標,座標範圍爲108–10^810810^8之間的整數。
N==0N==0時,測試文件結束。
【輸出】
對於每個測試用例,在單獨一行中輸出一個整數,表示圖形的面積。
【樣例輸入】

1
1 2 3 4
3
1 1 4 3
2 -1 3 2
4 0 5 2
0

【樣例輸出】

4
10

【數據範圍】
對於50%50\%的數據,0<N<=1000<N<=100,每個測試點測試數據組數<=20<=20
對於50%50\%的數據,座標範圍爲500–500500500之間的整數。
對於100%100\%的數據,0<N<=20000<N<=2000,每個測試點測試數據組數<=50<=50,座標範圍爲108–10^810810^8之間的整數。

線段樹掃描線

線段樹掃描線就是用來解決圖像面積並的問題。
例如存在下面三個矩形:
在這裏插入圖片描述
我們可以根據矩形的左右邊界分成多個部分求解:
在這裏插入圖片描述
在這裏插入圖片描述
這些紅色的線就稱爲掃描線

掃描線

從左往右掃過矩形左右邊,每兩條掃描線之間的面積總和就是圖形面積。
每一段面積 = (覆蓋掃描線上的長度和)*(與下一條掃描線的距離)。
可以發現,影響掃描線上的覆蓋長度,只與矩形左右邊相關,因此可以將圖形簡化:
在這裏插入圖片描述
構造一個四元組line(x,y,yy,flag)line(x,y,yy,flag),即結構體,來存在這些矩形左右邊。
xx:邊的橫座標;
y,yyy,yy:邊的兩個端點,長度爲yy-y;
flagflag11表示矩形的左邊界,1-1表示矩形的右邊界。

在掃描線上的覆蓋長度,可以看作邊在yy軸上的投影,我們將投影分成多段區間:
在這裏插入圖片描述
每一條邊的投影包含連續的多段區間,求掃描線的覆蓋總長度,其實就是多段區間的覆蓋長度。
如果將每段區間看作序列中的一個點,就相當於線段樹的區間修改、區間求和。
(1)構造一個線段樹維護的序列,每個點對應一個區間。
因爲縱座標yy有重複,需要去重排序。設去重排序後的序列爲dy[]dy[],有tt個。分成t1t-1段區間。
那麼第ii段區間的左端點爲dy[i]dy[i],區間長度爲dy[i+1]dy[i]dy[i+1]-dy[i]
在這裏插入圖片描述
再添加兩個數組:
cnt[k]cnt[k]:表示線段樹結點kk表示區間的覆蓋次數。
len[k]len[k]:表示線段樹結點kk表示覆蓋區間的長度。
(2)對於每一條邊line(x,y,yy,flag)line(x,y,yy,flag),相當於對覆蓋的區間結點進行增加flagflag
如何找到yyyyyy對應哪一段區間?
二分查找,因爲dy[]dy[]已經是去重且有序的。
需要注意邊界情況:
yy=Half()1yy=Half()-1yy>yyy>y,表示第Half()1Half()-1段區間,區間爲[dy[ef()1],dy[ef()])[ dy[ef()-1] , dy[ef()] )Half()Half()爲二分函數。
同時根據cnt[k]cnt[k]覆蓋次數更新結點:
當覆蓋次數爲00時,結點代表的區間不參與計算。

	if(cnt[k]!=0) len[k]=dy[r+1]-dy[l];			//進行了覆蓋,區間長度就是第l段~第r段區間的長度
	else len[k]=len[2*k]+len[2*k+1];			//沒有覆蓋,區間長度就是左右孩子區間長度之和

(3)區間求和,求投影到yy軸的覆蓋區間。
對於兩條邊line1(x1,y1,yy1,flag1)line2(x2,y2,yy2,flag2)line_1(x_1,y_1,yy_1,flag_1),line_2(x_2,y_2,yy_2,flag_2)
掃描線之間的寬度就是x2x1x_2-x_1
掃描線上的覆蓋長度是len[1]len[1]
兩條掃描線之間的面積:
len(x2x1)len*(x2-x1)
ans+=len(x2x1)ans+=len*(x2-x1)ansans就是最後的答案。

參考程序

#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;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章