扫描线+线段树

扫描线+线段树解决的是矩形覆盖求面积/周长问题

面积版:

也就是给出若干个矩形,最后求总面积(重点是快速解决覆盖问题)


矩形覆盖

三个矩形叠在一起就会产生重复部分,要怎么解决这个问题呢?
此类问题一般都是用线段树辅助扫描法来计算!

扫描线如下图所示,只要求出每一条扫描线的有效长度,就可以得出该区域的实际面积,最后把所有面积相加,即为该多边形的总面积。


扫描线

所以对于每一个矩形,只需要把矩形转换成上下两条平行线段即可(上边和下边)。

由此我们开一个结构体,记录每一条线段
struct node
{
    double l, r, h;//左端点,右端点,y轴(高度)
    int d;(记录上下边,上边为-1,下边为1)
} a[50000];

我们可以发现,当矩形非常散乱,或长度太长的时候,我们需要压缩一下各个上下边长来减少时间、空间复杂度,即离散化。

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]是整个区间,tree[1].len是整个区间的有效长度
}

由于我们要多次求取各个扫描线的有效长度,所以这里采用线段树的方法来求取。
那么问题来了,我们怎么知道那些线段要记录,哪些线段不必记录,或者是要清除呢?
这里我们的上下边的标记就发挥作用了
我们用s来记录标记之和,然后我们可以发现,当S==0时,有效长度为0,即清除该边界。


S的值

整个区间的有效长度

考虑到线段树,这里整个区间的有效长度即为tree[1].len;

线段树的构建
struct Node
{
    int tl, tr;
    int s;//该节点被覆盖的情况(是否完全覆盖)
    double len;//该区间被覆盖的总长度
} 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[ ]数组是从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两个作用
Ⅰ合并处理父节点的作用;
Ⅱ删除线段的作用(该标记线段的儿子一定为0,即父节点清零,即删除线段);


矩形覆盖求面积问题
总代码

#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;//该节点被覆盖的情况(是否完全覆盖)
    double len;//该区间被覆盖的总长度
} 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]是整个区间,tree[1].len是整个区间的有效长度
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",++Case, area);
    }
    return 0;
}



周长版:

通过扫描线辅助我们已经可以求取图形的面积了,那周长又该如何求解呢?
周长不同于面积,周长分为上下两边和左右两边。


求取周长

对于上下两边可以采取和求取面积相同的方法再加点小处理。
★★★ 边长=|本次扫描线的有效长度 - 上一次扫描线的有效长度|


边长
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;
}

解决完上下边,我们再来看看左右边,难道需要我们再做一条垂直的扫描辅助线?
当然,这里有更加简洁的方法。

首先我们现在改一下线段树保存的属性,我们用如下信息记录线段树的节点:
①l , r : 该节点代表的线段的左右端点坐标
②len : 这个区间被覆盖的长度(即计算时的有效长度)
③s : 表示这个区间被覆盖了几次
④lc , rc : 标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)
⑤num :这个区间有多少条线段(这个区间被多少条线段覆盖)

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

num的作用就是统计有多少个竖直边(一条上下边对应一条竖直边,最后处理再乘以2)。
当然会出现如下3种情况:

3种情况

由此我们可以得出:
tree[id].num=tree[id2].num+tree[id2+1].num-(tree[id2].rc&tree[id2+1].lc);
最后统计总的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容