掃描線+線段樹

掃描線+線段樹解決的是矩形覆蓋求面積/周長問題

面積版:

也就是給出若干個矩形项玛,最后求總面積(重點是快速解決覆蓋問題)


矩形覆蓋

三個矩形疊在一起就會產(chǎn)生重復(fù)部分挂据,要怎么解決這個問題呢仙粱?
此類問題一般都是用線段樹輔助掃描法來計算!

掃描線如下圖所示试疙,只要求出每一條掃描線的有效長度柳弄,就可以得出該區(qū)域的實際面積,最后把所有面積相加两蟀,即為該多邊形的總面積网梢。


掃描線

所以對于每一個矩形,只需要把矩形轉(zhuǎn)換成上下兩條平行線段即可(上邊和下邊)赂毯。

由此我們開一個結(jié)構(gòu)體战虏,記錄每一條線段
struct node
{
    double l, r, h;//左端點,右端點党涕,y軸(高度)
    int d;(記錄上下邊烦感,上邊為-1,下邊為1)
} a[50000];

我們可以發(fā)現(xiàn)膛堤,當(dāng)矩形非常散亂手趣,或長度太長的時候,我們需要壓縮一下各個上下邊長來減少時間肥荔、空間復(fù)雜度绿渣,即離散化。

double x[maxn];
……
for(i=1; i<=T; i++)
{
     double x1, y1, x2, y2;
     scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
     if(y1>y2) swap(y1,y2);
     a[m].l=x1, a[m].r=x2, a[m].h=y1, a[m].d=1;
     x[m++]=x1;
     a[m].l=x1, a[m].r=x2, a[m].h=y2, a[m].d=-1;
     x[m++]=x2;
}
sort(x, x+m);
sort(a, a+m, cmp);
int len=unique(x, x+m)-x;//去重
//build(1, 1, len);
double area=0.0;
for(i=0; i<m; i++)//離散化各點燕耿,并更新線段樹
{
      int l=lower_bound(x,x+len,a[i].l)-x+1;
      int r=lower_bound(x,x+len,a[i].r)-x;//左閉右開中符,與普通的線段樹記錄點不同,這里記錄的是線段
      //update(1, l, r, a[i].d);
      //area+=(a[i+1].h-a[i].h)*tree[1].len;//tree[1]是整個區(qū)間,tree[1].len是整個區(qū)間的有效長度
}

由于我們要多次求取各個掃描線的有效長度誉帅,所以這里采用線段樹的方法來求取淀散。
那么問題來了,我們怎么知道那些線段要記錄蚜锨,哪些線段不必記錄档插,或者是要清除呢?
這里我們的上下邊的標(biāo)記就發(fā)揮作用了
我們用s來記錄標(biāo)記之和踏志,然后我們可以發(fā)現(xiàn)阀捅,當(dāng)S==0時,有效長度為0针余,即清除該邊界饲鄙。


S的值

整個區(qū)間的有效長度

考慮到線段樹凄诞,這里整個區(qū)間的有效長度即為tree[1].len;

線段樹的構(gòu)建
struct Node
{
    int tl, tr;
    int s;//該節(jié)點被覆蓋的情況(是否完全覆蓋)
    double len;//該區(qū)間被覆蓋的總長度
} tree[50000];

void build(int id, int tl, int tr)
{
    tree[id].tl=tl;
    tree[id].tr=tr;
    tree[id].s=0;
    tree[id].len=0;
    if(tl==tr) return;
    else
    {
        int tm=(tl+tr)/2;
        build(id*2, tl, tm);
        build(id*2+1, tm+1, tr);
    }
}

void pushup(int id)
{
    if(tree[id].s)//去重
    {
        tree[id].len=x[tree[id].tr]-x[tree[id].tl-1];//tree[id].len=x[tree[id].tr+1-1]-x[tree[id].tl-1];加1是因為左閉右開,計算的時候要加回去忍级,減1是因為x[ ]數(shù)組是從0開始建立的帆谍,而tree[ ]是從1開始建立的!
    }
    else if(tree[id].tl==tree[id].tr)//點
    {
        tree[id].len=0;
    }
    else
    {
        tree[id].len=tree[id*2].len+tree[id*2+1].len;
    }
}

線段樹插入新的線段
void update(int id, int ql, int qr, int xx)
{
    int tl=tree[id].tl;
    int tr=tree[id].tr;
    if(ql<=tl && tr<=qr)
    {
        tree[id].s+=xx;
        pushup(id);
        return;
    }
    int tm=(tl+tr)/2;
    if(tm>=ql) update(id*2, ql, qr, xx);
    if(tm<qr) update(id*2+1, ql, qr, xx);
    pushup(id);
}

這里沒有pushdown轴咱,這是因為pushup兩個作用
Ⅰ合并處理父節(jié)點的作用汛蝙;
Ⅱ刪除線段的作用(該標(biāo)記線段的兒子一定為0,即父節(jié)點清零朴肺,即刪除線段)窖剑;


矩形覆蓋求面積問題
總代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

double x[50000];
struct node
{
    double l, r, h;
    int d;
} a[50000];

struct Node
{
    int tl, tr;
    int s;//該節(jié)點被覆蓋的情況(是否完全覆蓋)
    double len;//該區(qū)間被覆蓋的總長度
} tree[50000];

void build(int id, int tl, int tr)
{
    tree[id].tl=tl;
    tree[id].tr=tr;
    tree[id].s=0;
    tree[id].len=0;
    if(tl==tr) return;
    else
    {
        int tm=(tl+tr)/2;
        build(id*2, tl, tm);
        build(id*2+1, tm+1, tr);
    }
}

void pushup(int id)
{
    if(tree[id].s)//去重
    {
        tree[id].len=x[tree[id].tr]-x[tree[id].tl-1];
    }
    else if(tree[id].tl==tree[id].tr)//點
    {
        tree[id].len=0;
    }
    else
    {
        tree[id].len=tree[id*2].len+tree[id*2+1].len;
    }
}

void update(int id, int ql, int qr, int xx)
{
    int tl=tree[id].tl;
    int tr=tree[id].tr;
    if(ql<=tl && tr<=qr)
    {
        tree[id].s+=xx;
        pushup(id);
        return;
    }
    int tm=(tl+tr)/2;
    if(tm>=ql) update(id*2, ql, qr, xx);
    if(tm<qr) update(id*2+1, ql, qr, xx);
    pushup(id);
}

bool cmp(struct node X, struct node Y)
{
    return X.h<Y.h;
}

int main()
{
    int T, Case=0;
    while(~scanf("%d", &T)&&T)
    {
        int i, m=0;
        for(i=1; i<=T; i++)
        {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            if(y1>y2) swap(y1,y2);
            a[m].l=x1, a[m].r=x2, a[m].h=y1, a[m].d=1;
            x[m++]=x1;
            a[m].l=x1, a[m].r=x2, a[m].h=y2, a[m].d=-1;
            x[m++]=x2;
        }
        sort(x, x+m);
        sort(a, a+m, cmp);
        int len=unique(x, x+m)-x;//去重
        build(1, 1, len);
        double area=0.0;
        for(i=0; i<m; i++)//離散化各點,并更新線段樹
        {
            int l=lower_bound(x,x+len,a[i].l)-x+1;
            int r=lower_bound(x,x+len,a[i].r)-x;//左閉右開戈稿,與普通的線段樹記錄點不同西土,這里記錄的是線段
            update(1, l, r, a[i].d);
            area+=(a[i+1].h-a[i].h)*tree[1].len;//tree[1]是整個區(qū)間,tree[1].len是整個區(qū)間的有效長度
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",++Case, area);
    }
    return 0;
}



周長版:

通過掃描線輔助我們已經(jīng)可以求取圖形的面積了,那周長又該如何求解呢鞍盗?
周長不同于面積需了,周長分為上下兩邊和左右兩邊。


求取周長

對于上下兩邊可以采取和求取面積相同的方法再加點小處理般甲。
★★★ 邊長=|本次掃描線的有效長度 - 上一次掃描線的有效長度|


邊長
double Perimeter=0.0;
double last=0.0;
for(i=0; i<m; i++)
{
       update(1, l, r, a[i].d);
       Perimeter+=fabs(tree[1].len-last);
       last=tree[1].len;
}

解決完上下邊肋乍,我們再來看看左右邊,難道需要我們再做一條垂直的掃描輔助線敷存?
當(dāng)然墓造,這里有更加簡潔的方法。

首先我們現(xiàn)在改一下線段樹保存的屬性历帚,我們用如下信息記錄線段樹的節(jié)點:
①l , r : 該節(jié)點代表的線段的左右端點坐標(biāo)
②len : 這個區(qū)間被覆蓋的長度(即計算時的有效長度)
③s : 表示這個區(qū)間被覆蓋了幾次
④lc , rc : 標(biāo)記這個節(jié)點的左右兩個端點是否被覆蓋(0表示沒有滔岳,1表示有)
⑤num :這個區(qū)間有多少條線段(這個區(qū)間被多少條線段覆蓋)

struct Node
{
    int tl, tr;
    int s;
    int len, num;
    int lc, rc;
} tree[maxn];

num的作用就是統(tǒng)計有多少個豎直邊(一條上下邊對應(yīng)一條豎直邊,最后處理再乘以2)挽牢。
當(dāng)然會出現(xiàn)如下3種情況:

3種情況

由此我們可以得出:
tree[id].num=tree[id2].num+tree[id2+1].num-(tree[id2].rc&tree[id2+1].lc);
最后統(tǒng)計總的num,即:Perimeter+=(a[i+1].h-a[i].h)2tree[1].num;


矩形覆蓋求周長問題
總代碼

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;

double x[50000];
struct node
{
    double l, r, h;
    int d;
} a[50000];

struct Node
{
    int tl, tr;
    int s;
    int len, num;
    int lc, rc;
} tree[50000];

void build(int id, int tl, int tr)
{
    tree[id].tl=tl;
    tree[id].tr=tr;
    tree[id].s=0;
    tree[id].len=0;
    tree[id].lc=0;
    tree[id].rc=0;
    tree[id].num=0;
    if(tl==tr) return;
    else
    {
        int tm=(tl+tr)/2;
        build(id*2, tl, tm);
        build(id*2+1, tm+1, tr);
    }
}

void pushup(int id)
{
    if(tree[id].s)
    {
        tree[id].len=x[tree[id].tr]-x[tree[id].tl-1];
        tree[id].lc=tree[id].rc=1;
        tree[id].num=1;
    }
    else if(tree[id].tl==tree[id].tr)
    {
        tree[id].len=0;
        tree[id].lc=tree[id].rc=0;
        tree[id].num=0;
    }
    else
    {
        tree[id].len=tree[id*2].len+tree[id*2+1].len;
        tree[id].lc=tree[id*2].lc;
        tree[id].rc=tree[id*2+1].rc;
        tree[id].num=tree[id*2].num+tree[id*2+1].num-(tree[id*2].rc&tree[id*2+1].lc);
    }
}

void update(int id, int ql, int qr, int xx)
{
    int tl=tree[id].tl;
    int tr=tree[id].tr;
    if(ql<=tl && tr<=qr)
    {
        tree[id].s+=xx;
        pushup(id);
        return;
    }
    int tm=(tl+tr)/2;
    if(tm>=ql) update(id*2, ql, qr, xx);
    if(tm<qr) update(id*2+1, ql, qr, xx);
    pushup(id);
}

bool cmp(struct node X, struct node Y)
{
    return X.h<Y.h;
}

int main()
{
    int T, Case=0;
    while(~scanf("%d", &T)&&T)
    {
        int i, m=0;
        for(i=1; i<=T; i++)
        {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            if(x1>x2) swap(x1,x2);
            if(y1>y2) swap(y1,y2);
            a[m].l=x1, a[m].r=x2, a[m].h=y1, a[m].d=1;
            x[m++]=x1;
            a[m].l=x1, a[m].r=x2, a[m].h=y2, a[m].d=-1;
            x[m++]=x2;
        }
        sort(x, x+m);
        sort(a, a+m, cmp);
        int len=unique(x, x+m)-x;
        build(1, 1, len);
        double C=0.0;
        double last=0.0;
        for(i=0; i<m; i++)
        {
            int l=lower_bound(x,x+len,a[i].l)-x+1;
            int r=lower_bound(x,x+len,a[i].r)-x;
            update(1, l, r, a[i].d);
            C+=fabs(tree[1].len-last);
            C+=(a[i+1].h-a[i].h)*2*tree[1].num;
            last=tree[1].len;
        }
        printf("%.0f\n", C);
    }
    return 0;
}

參考:http://m.blog.csdn.net/tomorrowtodie/article/details/52048323

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摊求,一起剝皮案震驚了整個濱河市禽拔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌室叉,老刑警劉巖睹栖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茧痕,居然都是意外死亡野来,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門踪旷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曼氛,“玉大人豁辉,你說我怎么就攤上這事∫ɑ迹” “怎么了徽级?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長聊浅。 經(jīng)常有香客問我餐抢,道長,這世上最難降的妖魔是什么低匙? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任旷痕,我火速辦了婚禮,結(jié)果婚禮上顽冶,老公的妹妹穿的比我還像新娘欺抗。我一直安慰自己,他們只是感情好渗稍,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布佩迟。 她就那樣靜靜地躺著,像睡著了一般竿屹。 火紅的嫁衣襯著肌膚如雪报强。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天拱燃,我揣著相機與錄音秉溉,去河邊找鬼。 笑死碗誉,一個胖子當(dāng)著我的面吹牛召嘶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哮缺,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼弄跌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尝苇?” 一聲冷哼從身側(cè)響起铛只,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糠溜,沒想到半個月后淳玩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡非竿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年蜕着,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片红柱。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡承匣,死狀恐怖蓖乘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悄雅,我是刑警寧澤驱敲,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站宽闲,受9級特大地震影響众眨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜容诬,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一娩梨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧览徒,春花似錦狈定、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躲叼,卻和暖如春芦缰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枫慷。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工让蕾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人或听。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓程剥,卻偏偏與公主長得像脚猾,于是被迫代替她去往敵國和親疯暑。 傳聞我的和親對象是個殘疾皇子恍涂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 引子 每天我們晚上加班回家,可能都會用到滴滴或者共享單車足丢。打開 app 會看到如下的界面: app 界面上會顯示出...
    一縷殤流化隱半邊冰霜閱讀 45,242評論 51 187
  • 前一節(jié)OpenCV For iOS(四-上): 人臉檢測及分類器的訓(xùn)練說到分類器使用的 LBP Haar,這里補充...
    hehtao閱讀 2,496評論 0 3
  • UIBezierPath Class Reference 譯:UIBezierPath類封裝了Core Graph...
    鋼鉄俠閱讀 1,706評論 0 3
  • 不知不覺的元镀,已經(jīng)堅持寫了好多天!我要為自己點贊加油霎桅!昨天處理的突發(fā)事件請假了,今天可能也補不上讨永。但是還是努力的堅持...
    汽球閱讀 193評論 0 0
  • 夜沉沉滔驶,絲絲寒意掠過心房 流年,亂了思念 風(fēng)卿闹,吹滅內(nèi)心最后一線光亮 美麗的夢揭糕,遺落在了誰的城萝快? 往事蒙上幾多風(fēng)霜 ...
    芙蓉苑閱讀 221評論 1 2