矩形面積並(POJ 1151)
阿新 • • 發佈:2019-01-26
problem
給你N個矩形,求這些矩陣的面積並 N
思路
“掃描線思路”+離散化+線段樹維護
關於離散化
由於座標範圍較大,需要使其更“緊密”,這樣才能用線段樹處理
離散化的核心思想就是 相對大小不變 且能通過現在的值確定之前的值
所以離散化的方法就是(以y軸座標為例) 將y軸座標塞入id陣列(2*n個) 排序
將之前每個事件的y軸座標替換為在id陣列中的下標 (二分去找) 這樣就完成了離散化
關於線段樹維護
將事件分成兩類,即入事件和出事件。
對於入事件,我們將區間cov值+1,即對應區間標記+1
對於出事件,我們將區間cov值-1 ,即對應區間標記-1
每一次處理過一個事件後,對於總區間,我們維護的sum[1]的值為那些標記不為0的區間所對應的長度,這些長度可以作為面積並的長,高即為
當再處理一個事件時,如果它是出事件,那麼自然會更新對應區間cov的值-1,剩下的不為0的區間又可以作為面積並的長,以此類推……
兩種不同的寫法
注意到,這裡處理的是區間,即最小單位是長度為1的區間(離散化後)。但是一般的線段樹其葉子節點就表示那個點,而不是區間。
舉個例子,比如[3,4]區間+1,一般寫法會將葉子節點3和葉子節點4的值+1,再pushup。但這裡我們沒法處理這樣跨結點的區間,因為要麼兩邊都加(超了),要麼都return (少了)。即對於點線段樹遞迴區間時要採用左閉右開
的方法。所以有兩種寫法:
- ①點線段樹,需要左閉右開處理
- ②區間線段樹,葉子節點可表示區間,需要改變遞迴區間的邊界
下面是兩種方法的程式碼實現,細節見程式碼註釋
程式碼示例1(點線段樹)
//沿y軸方向掃描 對x軸座標離散化後用線段樹維護
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1//這裡葉子節點還是表示點而不是區間,這也是通常的做法
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形數量的兩倍"
int n;
int cov[maxn<<2];//區間覆蓋情況
double id[maxn],sum[maxn<<2];//id用於離散化
struct event{
double l,r,h;//h為縱座標,掃描線平行於x軸
int f;
event(){}//必須要加
event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
bool operator < (const event & rhs) const{//按照h進行排序
return h < rhs.h;
}
}evt[maxn];
void pushup(int l,int r,int rt){
//l r這個區間對應的rt 若cov[rt]!=0 也即>=1 當然其不可能<0
if(cov[rt]) sum[rt]=id[r+1]-id[l];//左閉右開 注意是r+1
else if(l==r) sum[rt]=0;//注意點是沒有長度的
else sum[rt]=sum[rt<<1]+sum[rt<<1|1];//不是葉子節點,從下往上更新
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l && r<=R){//當前節點區間在目的區間內
cov[rt]+=c;
pushup(l,r,rt);
return ;
}
int m=(l+r)>>1;
if(L<=m) update(L,R,c,lson);
if(R>m) update(L,R,c,rson);
pushup(l,r,rt);
}
int main()
{
int cas=1;
while(~scanf("%d",&n),n){
for(int i=1;i<=n;++i){
double x1,y1,x2,y2;//左上和右下
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
evt[i]=event(x1,x2,y1,-1);
evt[i+n]=event(x1,x2,y2,1);
id[i]=x1;
id[i+n]=x2;
}
sort(evt+1,evt+2*n+1);
sort(id+1,id+2*n+1);
int m=unique(id+1,id+2*n+1)-id-1;//其實可以不去重
memset(cov,0,sizeof(cov));
memset(sum,0,sizeof(sum));
double ans=0;
for(int i=1;i<2*n;i++){
int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意線段樹下標從1開始
int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
if(l<=(r-1)) update(l,r-1,evt[i].f,1,m,1);//注意是r-1 套路
ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]記錄了當前的“有效長度”
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
}
return 0;
}
程式碼示例2(區間線段樹) 注意第7行 、第39行
//1是沿y軸方向掃描 對x軸座標離散化後用線段樹維護
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m,r,rt<<1|1//這裡葉子節點表示最小區間
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形數量的兩倍"
int n;
int cov[maxn<<2];//區間覆蓋情況
double id[maxn],sum[maxn<<2];//id用於離散化
struct event{
double l,r,h;//h為縱座標,掃描線平行於x軸
int f;
event(){}//必須要加
event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
bool operator < (const event & rhs) const{//按照h進行排序
return h < rhs.h;
}
}evt[maxn];
void pushup(int l,int r,int rt){
//l r這個區間對應的rt 若cov[rt]!=0 也即>=1 當然其不可能<0
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
if(cov[rt]) sum[rt]=id[r]-id[l];//注意是r就行 因為是區間線段樹 比較自然
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l && r<=R){//當前節點區間在目的區間內
cov[rt]+=c;
pushup(l,r,rt);
return ;
}
int m=(l+r)>>1;
if(L<m) update(L,R,c,lson);//注意這裡是< 沒有等於號
if(R>m) update(L,R,c,rson);
pushup(l,r,rt);
}
int main()
{
int cas=1;
while(~scanf("%d",&n),n){
for(int i=1;i<=n;++i){
double x1,y1,x2,y2;//左上和右下
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
evt[i]=event(x1,x2,y1,-1);
evt[i+n]=event(x1,x2,y2,1);
id[i]=x1;
id[i+n]=x2;
}
sort(evt+1,evt+2*n+1);
sort(id+1,id+2*n+1);
int m=unique(id+1,id+2*n+1)-id-1;//其實可以不去重
memset(cov,0,sizeof(cov));
memset(sum,0,sizeof(sum));
double ans=0;
for(int i=1;i<2*n;i++){
int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意線段樹下標從1開始
int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
update(l,r,evt[i].f,1,m,1);//注意是r 比較自然
ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]記錄了當前的“有效長度”
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
}
return 0;
}
這裡都是沿y軸方向掃描 對x軸座標離散化後用線段樹維護的;當然也可以反過來