掃描線算法講解+例題

會遇到這種題:給你很多矩形,如果一個區域被多次覆蓋,只計算一次,問總面積.

一直說是線段樹,但是不知道是掃描線,寫篇博客記錄一下.

掃描線是這麼個意思

肯定需要離散化了,可以選擇離散化x軸或者y軸的點,然後就分割成了幾個區間,x1到x2,x2到x3,x3到x4,我們以xi代表以它爲起點,以下一個離散化的點爲終點的線段.

然後我們有四個高度,y1,y2,y3,y4,可以知道,面積等於(y2-y1)*(x3-x1)+.........這樣面積實際上就可控了.

那麼具體操作怎麼辦捏,以離散化x軸爲栗子,我們在輸入的時候可以找到一個上邊和下邊,我們把下邊標記爲1,上邊標記-1,

排序之後,y1,2,3,4順次排序,

現在開始處理:

這是第一次的情況,y1是下底邊,[x1,x3)被標記爲1,然後乘以這段的高.以HDU1542的樣例爲假設,

樣例:

2
10 10 20 20
15 15 25 25.5
0

則黃色部分就是(20-10)*(15-10) = 50;

第二次的情況:

現在我們掃描了y2,此時我們的對x2,x3進行了掃描,現在橫軸長度變爲[x1,x4),爲什麼捏,因爲之前的圖一,黃色部分覆蓋了[x1,x3),現在[x2,x3)被掃描兩次,[x1,x2)被掃描一次,所以,此時的面積是藍色部分加上和藍色部分平行的黃色部分,是(20-15(高))*(25-10(長)) = 75.

第三次掃描,是對y3進行掃描,之前都是下邊界,現在遇到上邊界-1的情況了.

 

下面的箭頭就是這個上邊界掃描完產生的變化,然後面積就是黃色部分加起來,再之後是y4的掃描,-1之後等於0,可掃可不掃.

//Atlantis HDU - 1542 
#include<bits/stdc++.h>
using namespace std;
const int maxn = 505;
int lazy[maxn<<2];
int add[maxn<<2];
double x[maxn<<2],sum[maxn<<2];
struct EDGE
{
    int ss;//上下邊
    double l,r,h;//左右,高度
    EDGE(double _x1=0,double _x2=0,double _y=0,int _up_or_down=0)
    {
        l = _x1;
        r = _x2;
        h = _y;
        ss = _up_or_down;
    }
    bool operator<(const EDGE & a)
    {
        return h<a.h;
    }
} dian[maxn];
void pushUP(int rt,int l,int r)
{
    if(add[rt])//如果有標記,則長度爲標記的長度
        sum[rt] = x[r+1]-x[l];
    else if(l == r)//相等則爲長度0
        sum[rt] = 0;
    else sum[rt] = sum[rt<<1]+sum[rt<<1|1];//等於左邊+右邊的sum
}
void update(int rt,int L,int R,int val,int l,int r)//更新[l,r]區間
{
    int mid;
    if(L<=l && R >= r)
    {
        add[rt] += val;
        pushUP(rt,l,r);
        return;
    }
    mid = (l+r)>>1;
    if(L<=mid)
        update(rt<<1,L,R,val,l,mid);
    if(R>mid)
        update(rt<<1|1,L,R,val,mid+1,r);
    pushUP(rt,l,r);
}
int main()
{
//    freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int n;
    int kase = 1;
    double x1,y1,x2,y2,ans;
    while(cin>>n && n)
    {
        ans = 0;
        int top = 0,l,r;
        printf("Test case #%d\n",kase++);
        printf("Total explored area: ");
        for(int i=0; i<n; i++)
        {
            cin>>x1>>y1>>x2>>y2;
            x[top] = x1;
            dian[top++] = EDGE(x1,x2,y1,1);//下邊界標記爲1
            x[top] = x2;
            dian[top++] = EDGE(x1,x2,y2,-1);//上邊界標記爲-1
        }
        sort(x,x+top);
        sort(dian,dian+top);
        int k = 1;
        for(int i=1; i<top; i++)
        {
            if(x[i] != x[i-1])
                x[k++] = x[i];//序列離散化變成[0,k);
        }
        memset(add,0,sizeof(add));
        memset(sum,0,sizeof(sum));
        for(int i=0; i<top-1; i++)
        {
            l = lower_bound(x,x+k,dian[i].l)-x;//下邊界
            r = lower_bound(x,x+k,dian[i].r)-x-1;//上邊界,因爲每個點代表的是以當前點爲起點的下一段,所以要-1
            update(1,l,r,dian[i].ss,0,k-1);//更新[0,k-1]區間
            ans += (sum[1]*(dian[i+1].h-dian[i].h));
        }
        printf("%.2f\n\n",ans);
    }
    return 0;
}

然後開始解析代碼職能,sum需要統計的是add標記過的長度,add是標記現在當前段被幾個下邊界+1,我們的邊需要按照高度從小到大排序,ss表示+1和-1,sum[1]就是被標記的有效長度,然後由於區間是[)的,所以r lower_bound之後需要-1,

關於去重:據說可去可不去,意義:把x軸離散化之後變成有序的,相當於打個映射.二分的時候直接是嚴格單調遞增的,其他的沒有影響.

 

變形題:

Rectangles Gym - 101982F

當時打死都不會,還以爲是二維線段樹,其實好像沒這東西,全是自己瞎猜的.

現在要處理的問題比較狗了一點,還是那麼掃描,但是負負得正,正正得負,記得一個運算,^運算,所以我們現在+val應該改成^1;

//【掃描線】Gym - 101982 - F - Rectangles
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;
int lazy[maxn<<3];
int add[maxn<<3];
int x[maxn<<2],sum[maxn<<3];
struct EDGE
{
    int ss;//上下邊
    int l,r,h;//左右,高度
    EDGE(int _x1=0,int _x2=0,int _y=0)
    {
        l = _x1;
        r = _x2;
        h = _y;
    }
    bool operator<(const EDGE & a)
    {
        return h<a.h;
    }
} e[maxn<<2];
void cal(int rt,int l,int r)//長度裏面減去之前有標記的
{
    sum[rt] = (x[r]-x[l])-sum[rt];
}
void pushUP(int rt)//向上更新正常求和
{
    sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void pushDown(int rt,int l,int r)//向下更新
{
    if(!add[rt])return;
    add[rt<<1] ^= 1;
    add[rt<<1|1] ^= 1;
    int mid = (l+r)>>1;
    cal(rt<<1,l,mid);//計算sum
    cal(rt<<1|1,mid,r);
    add[rt] = 0;
}
void update(int rt,int L,int R,int l,int r)//更新[L,R]區間
{
    int mid;
    if(L<=l && R >= r)//如果rt包含的區間完全被覆蓋
    {
        add[rt] ^= 1;
        cal(rt,l,r);
        return;
    }
    pushDown(rt,l,r);//向下更新一次懶惰標記
    mid = (l+r)>>1;
    if(L<mid)
        update(rt<<1,L,R,l,mid);
    if(R>mid)
        update(rt<<1|1,L,R,mid,r);
    pushUP(rt);
}
int main()
{
    int n;
    scanf("%d",&n);
    int x1,x2,y1,y2,all=0;
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        e[2*i-1].l=min(x1,x2);//l嚴格小於r
        e[2*i-1].r=max(x1,x2);
        e[2*i-1].h=min(y1,y2);
        e[2*i].l=min(x1,x2);
        e[2*i].r=max(x1,x2);
        e[2*i].h=max(y1,y2);
        x[2*i-1]=x1,x[2*i]=x2;
    }
    sort(x+1,x+1+n*2);
    all=unique(x+1,x+1+n*2)-x-1;
    for(int i=1; i<=2*n; i++)//處理出來l和r在離散化之後的x軸上的區間
        e[i].l=lower_bound(x+1,x+1+all,e[i].l)-x,e[i].r=lower_bound(x+1,x+1+all,e[i].r)-x;
    sort(e+1,e+1+2*n);
    LL ans=0;
    for(int i=1; i<2*n; i++)
    {
        update(1,e[i].l,e[i].r,1,all);//更新這條線段
        ans+=(LL)sum[1]*(e[i+1].h-e[i].h);
    }
    printf("%lld\n",ans);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章