HDU-1542/POJ-1151 Atlantis(矩形并面积--线段树+离散化)

13 篇文章 0 订阅
12 篇文章 0 订阅
题目链接

HDU - 1542 or POJ - 1151

题目大意

以对角线的形式给出 n 个矩形,给出左下点(x1,y1)和右上点 (x2,y2) ,double型。求 n 个矩形的并面积。(多组)

数据范围

1n1000x1<x21000000y1<y2100000

解题思路

第一次做线段树扫描线的题,方便自己以后忘了好看,所以打算写一写自己的理解。
有图有真相,虽说图很丑,将就一下。
扫描线,顾名思义,就是有一根假象的线在扫描矩形,就像扫二维码的那根线一样,把所有矩形扫一遍,就知道矩形的并面积了。可以上下扫,也可以左右扫,大同小异,我采用的是上下扫。
给3个矩形,如图:

上下扫描是对 x 轴建立线段树,所以与y轴平行的边就没用了,就可以去掉了,如下图:
这里写图片描述
可以看出,所有矩形的覆盖总区间为 [1,8] ,就对这个区间建立线段树。
然后想象有一根线平行于 x 轴,从下往上扫描。
定义一个 lazy 数组, lazy[rt] 表示 rt 节点所管辖区间被覆盖了多少次, lazy[rt]==0 就表示没有被覆盖。每条矩形的边都有一个 flag ,底边 flag1 ,顶边 flag1 ,并将所有边按照纵坐标从小到大排好序。
扫描到一条底边的时候,就将其所对应的区间 [L,R]flag 修改,表示被覆盖。扫描到顶边的时候,则其对应区间 [L,R] 的覆盖次数减少一次。这时, flag=±1 的用处就出来了,修改区间的时候直接让其 lazy[]+=flag 就好了。
除此之外,还需要得到总区间的总覆盖长度,代码体现在那个 len 上。

准备工作做完了,开始计算面积。记 L 为总区间的总覆盖长度ans为最后要求的矩形并面积。
现扫描到第一条边,修改对应区间,并更新得到 L 。令H为当前这条边与下一条边的高度差,即 H=seg[i+1].hseg[i].h
如图,求得第一部分面积:
这里写图片描述
此时 H==1,L==3ans+=HLans+=3
紧接着扫描到第二条线,计算第二部分面积,如图:
这里写图片描述
此时 H==1,L=L1+L2==3+3==6ans+=6 。后几部分如图:
这里写图片描述
ans+=7
这里写图片描述
ans+=5
这里写图片描述
ans+=3
由此看出,只用扫描 (n1) 条边就可以计算出面积并了最后得出 ans==24


坐标最多可达 1e5 ,所以,离散化一下为好。
另外,我采用的是区间化点法(自创名词)建立的线段树。
再者,推荐两篇比较好的博文:onetwo

详见代码:

//线段树--矩形面积求并
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 2005;

int n, m, k, cas;
struct Segment
{
    double xl, xr;  //分别表示这条横线的左右端点坐标
    double h;       //这条横线的纵坐标
    int flag;       //flag为1表示这条横线是矩形的下边,-1就是上边
    Segment() {}    //这种操作要学到
    Segment(double a, double b, double c, int d) : xl(a), xr(b), h(c), flag(d) {}
    bool friend operator < (Segment a, Segment b) {
        if(a.h == b.h) return a.flag > b.flag;
        else return a.h < b.h;
    }
}seg[2 * MaxN + 5];

double x[2 * MaxN + 5]; //存离散去重后的横坐标

struct segtree
{
    int l, r;
    double len;
    //当前区间被覆盖的长度,以此回溯得到总区间被覆盖的长度,即tree[1].len
}tree[8 * MaxN + 5];
int lazy[8 * MaxN + 5];
//lazy[rt]表示rt所管辖的区间被覆盖了多少次

void Build(int rt, int l, int r) {
    tree[rt].l = l, tree[rt].r = r;
    tree[rt].len = 0.0;
    lazy[rt] = 0;
    if(l == r) return;
    int mid = (l + r) >> 1;
    Build(rt << 1, l, mid);
    Build(rt << 1 | 1, mid + 1, r);
}

int bin_search(double val) { //查找val在x数组中的位置
    int l = 1, r = k;
    int mid = 0, res = 0;
    while(l <= r) {
        mid = (l + r) >> 1;
        if(x[mid] >= val) res = mid, r = mid - 1;
        else l = mid + 1;
    }
    return res;
}

void push_up(int rt) {
    if(lazy[rt] > 0) { //若当前区间被覆盖,则更新该区间的len
        tree[rt].len = x[tree[rt].r + 1] - x[tree[rt].l];
        return;
    }
    tree[rt].len = tree[rt << 1].len + tree[rt << 1 | 1].len; //回溯
}

void update(int rt, int L, int R, int f) {
    if(L <= tree[rt].l && tree[rt].r <= R) {
        lazy[rt] += f;
        push_up(rt);
        return ;
    }
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if(L <= mid) update(rt << 1, L, R, f);
    if(R > mid) update(rt << 1 | 1, L, R, f);
    push_up(rt);
}

int main()
{
    cas = 0;
    while(scanf("%d", &n) != EOF) 
    {
        if(n == 0) break;
        m = 0;
        for(int i = 1; i <= n; i++) {
            double x1, y1, x2, y2;
            scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
            x[++m] = x1;
            seg[m] = Segment(x1, x2, y1, 1);
            x[++m] = x2;
            seg[m] = Segment(x1, x2, y2, -1);
        } //m == 2 * n
        sort(x + 1, x + m + 1);
        sort(seg + 1, seg + m + 1);
        k = 1;
        for(int i = 2; i <= m; i++) //离散化去重
            if(x[i] != x[i + 1])
                x[++k] = x[i];
        Build(1, 1, k); //建树
        double ans = 0.0;
        for(int i = 1; i <= m - 1; i++) {
            int L = bin_search(seg[i].xl);      //查找当前线段左端点在x数组中的位置
            int R = bin_search(seg[i].xr) - 1;  //同理,查找右端点
            update(1, L, R, seg[i].flag);       //更新区间
            ans += tree[1].len * (seg[i + 1].h - seg[i].h);
        }
        printf("Test case #%d\n", ++cas);
        printf("Total explored area: %.2lf\n", ans);
        printf("\n");
        memset(x, 0, sizeof(x));
    }
    return 0;
}



进阶版矩形交面积

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值