1. 程式人生 > >矩形面積並(POJ 1151)

矩形面積並(POJ 1151)

problem

給你N個矩形,求這些矩陣的面積並 N1e5

思路

“掃描線思路”+離散化+線段樹維護

關於離散化

由於座標範圍較大,需要使其更“緊密”,這樣才能用線段樹處理

離散化的核心思想就是 相對大小不變 且能通過現在的值確定之前的值

所以離散化的方法就是(以y軸座標為例) 將y軸座標塞入id陣列(2*n個) 排序

將之前每個事件的y軸座標替換為在id陣列中的下標 (二分去找) 這樣就完成了離散化

關於線段樹維護

將事件分成兩類,即入事件和出事件。

對於入事件,我們將區間cov值+1,即對應區間標記+1

對於出事件,我們將區間cov值-1 ,即對應區間標記-1

每一次處理過一個事件後,對於總區間,我們維護的sum[1]的值為那些標記不為0的區間所對應的長度,這些長度可以作為面積並的長,高即為evt[i+1].hevt[i].h

當再處理一個事件時,如果它是出事件,那麼自然會更新對應區間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軸座標離散化後用線段樹維護的;當然也可以反過來