计蒜客 17313 Overlapping Rectangles

19 篇文章 0 订阅
10 篇文章 0 订阅

Problem

nanti.jisuanke.com/t/17313

Reference

扫描线算法完全解析
矩形面积并、矩形面积交、矩形周长并(线段树、扫描线总结)
POJ1151(线段树+扫描线求矩形面积并)
算法总结:【线段树+扫描线】&矩形覆盖求面积/周长问题(HDU 1542/HDU 1828)

Meaning

平面上有若干个矩形,可能有重叠,问总的面积是多少(重叠部分只算一次)

Analysis

这是扫描线求矩形面积并的裸题。
总的思路就是把几个重叠在一起的矩形所组成的多边形,切成一个个小矩形,分别统计答案。
split
以从下往上扫描为例(上图来自:poj 1151 Atlantis】线段树之扫描线(面积并))。关注矩形的上、下边,这些边(看作分界线)把平面分成若干个区间,每个区间都报包含一个切出来的小矩形。这个矩形的高,就是相邻两条分界线之间的距离,而底边长就是在当前扫描线(即上图红线)上的那些边并起来的长度(即处于红线上的矩形边的并),底乘高就是这块被切出来的小矩形的面积。
怎么统计扫描线上的总长度呢?用线段树。
在从下往上扫的过程中(扫描,其实就先是把所有矩形上、下边放到一起,按 y 轴坐标从低到高拍序,然后从低到高地遍历这些边,也即遍厉分界线),每遇到一条边,看它是上边还是下边。如果是下边,就把这条边“并”进总长里;上边则删掉这部分,即抵消到当初上边并进来的那部分。
这份代码里,线段树的叶子对应的是“点”,而不是“区间”,至少两个点才能夹出一个区间,所以当区间长度只有 2 的时候,不应再往下搜树。
也可以改写成叶子对应一个区间的写法。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000;

struct seg
{
    int l, r; // 边的左、右边界坐标
    int h; // 边的高度
    int v; // 标记上下边:1 -> 下边,-1 -> 上边

    seg(int _l = 0, int _r = 0, int _h = 0, int _v = 0):
        l(_l), r(_r), h(_h), v(_v) {}

    bool operator < (const seg &rhs) const
    {
        return h < rhs.h;
    }
} s[N<<1];

int x[N<<1], cnt[N<<3], len[N<<3];

void pushup(int l, int r, int rt)
{
    if(cnt[rt])
        len[rt] = x[r] - x[l];
    else if(l + 1 >= r) // 已是最小区间,没有子区间
        len[rt] = 0;
    else
        len[rt] = len[rt<<1] + len[rt<<1|1];
}

void update(int ul, int ur, int v, int l, int r, int rt)
{
    if(ul <= l && r <= ur)
    {
        cnt[rt] += v;
        pushup(l, r, rt);
        return;
    }
    int m = l + r >> 1;
    if(ul < m) // 等于的时候也是不需要的,因为一个点不是区间
        update(ul, ur, v, l, m, rt<<1);
    if(ur > m) // 同样不取等号
        update(ul, ur, v, m, r, rt<<1|1); // [m,r]!不是[m+1,r]!
    pushup(l, r, rt);
}

int main()
{
    int n;
    while(~scanf("%d", &n) && n)
    {
        for(int i = 0, xl, xr, yl, yr; i < n; ++i)
        {
            scanf("%d%d%d%d", &xl, &yl, &xr, &yr);
            // 下边是 1,表示并入
            s[i] = seg(xl, xr, yl, 1);
            // 下边-1,表示删除
            s[i+n] = seg(xl, xr, yr, -1);
            x[i] = xl;
            x[i+n] = xr;
        }
        n <<= 1;
        // 边从低到高排
        sort(s, s + n);
        sort(x, x + n);
        memset(len, 0, sizeof len);
        memset(cnt, 0, sizeof cnt);
        int m = unique(x, x + n) - x, ans = 0;
        for(int i = 0, l, r; i < n - 1; ++i)
        {
            // 离散化横坐标
            l = lower_bound(x, x + m, s[i].l) - x;
            r = lower_bound(x, x + m, s[i].r) - x;
            // 并入或删除边
            update(l, r, s[i].v, 0, m, 1);
            // 加入小矩形面积
            ans += len[1] * (s[i+1].h - s[i].h);
        }
        printf("%d\n", ans);
    }
    puts("*");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值