HDU1542 Atlantis (线段树 + 扫描线)

题目链接:Atlantis

大致题意

现在有平面直角坐标系xoy, 有n个平行于x轴y轴的矩形, 给出这些矩形的左上角和右下角的坐标, 让你求出这些矩形组成的图形的总面积.

解题思路

本题是 线段树 + 扫描线 的典型例题, 是十分经典的模版题.

分析:

例如需要求下图所示的矩形的面积总和:
在这里插入图片描述

我们不妨将所有矩形的所有边都延长, 然后将整个坐标轴划分成一个个小区域:

在这里插入图片描述

然后再将这些小区域按照垂直于x轴的直线再次划分成一个个小矩形(如下图阴影所示):

在这里插入图片描述

现在我们需要计算的总面积可以拆分成图中阴影部分的面积之和, 这样我们如果从小往大枚举x的取值, 必然每两个x1与x2之间会有一个小矩形, 我们只需要把这些小矩形的面积加起来即可.

我们发现, 每两个相邻的x1与x2我们可以通过把x的坐标排序后得到, 那在y轴部分的长度如何计算呢?
这时候就用到了我们的线段树, 我们需要用线段树维护在当前[x1, x2]区间, 小矩形在y轴上线段的总长度.

做法:

维护每两条平行于x轴的线段, 线段树内部记录当前区间出现的次数, 以及当前区间已经覆盖的总长度, 以及懒标记.

假设离散化后用到的y轴坐标有len个, 那么我们就需要维护len-1条线段. 每个矩形必然有两条平行于y轴的线, 在我们从小到大枚举x的过程中, 当我们遇到左侧的线段时, 我们给对应的线段**+1**, 当遇到右侧时, 则**-1**.
如果当前区间的线段有出现过, 则总长度就要加上这段线段的长度.

这里其实有很多的细节问题, 如: 对于当前区间+1无所谓, 因为:
​ ①如果当前区间没有覆盖过, 则覆盖的长度就等于当前区间的长度
​ ②如果当前区间覆盖过, 则多覆盖一次也不影响当前区间覆盖的长度.

而如果是区间-1, 则出现问题了, 我们如何把懒标记下传? 当前区间-1, 则其子区间每个都需要-1, 有的会减为0, 有的则不会. 我们总不能每次都遍历到叶子结点. (但是有方法是可以合理实现的QAQ)

优化:

由于上面维护懒标记的方法太麻烦了, 我们这里说一下不用懒标记实现的方法.

需要用到而线段树+扫描线问题比较特殊, 由于扫描线特殊的性质, 我们每次修改时, +1和-1的操作是成对的, 对于这样成对的操作, 每次修改时下放的区间也是相同的. 所以我们不妨不去记录懒标记, 而去维护一个特殊的值, 这个值只表示这个区间是否被覆盖, 不再向下进行传递.

这样对于每次修改后, 如果当前区间被覆盖, 那么当前区间的长度就为所包含的线段长度. 反之则为左右儿子所包含的线段长度.

那么对于查询操作, 因为每次都只查询根节点, 而我们的pushup一定可以保证根节点的是最新的, 所以也是符合要求的.

结论:

对于这类特殊的 线段树 + 扫描线 的题目, 我们可以不用懒标记, 用个值去记录当前区间出现过几次即可.

特别的, 要特别注意线段树内部维护每个区间内部是线段! 上文中所说的区间也都只是线段树中区间中所包含的线段.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 110; //矩形个数
vector<double> v;
int find(double x) { return lower_bound(v.begin(), v.end(), x) - v.begin(); }
struct LINE { //垂直于x轴的线段记录.
    double x, y1, y2; int c; //c表示矩形左侧线段还是右侧
    bool operator< (const LINE& t) const { return x < t.x; }
}; vector<LINE> line; //若不用vector, 应开2倍空间


struct node {
    int l, r;
    int cou; //cou的意义是, 当前区间被覆盖几次(不下传)
    double len;
}t[N << 3]; //特别注意, 应该开8倍空间!! 扫描线本身2倍 + 线段树自身4倍 = 8倍;
double getlen(int l, int r) { return v[r + 1] - v[l]; }
void pushup(int x) {
    if (t[x].cou) t[x].len = getlen(t[x].l, t[x].r);
    else if (t[x].l != t[x].r) t[x].len = t[x << 1].len + t[x << 1 | 1].len;
    else t[x].len = 0;
}

void build(int l, int r, int x = 1) {
    t[x] = { l, r, 0, 0 };
    if (l == r) return;
    int mid = l + r >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    //不用pushup
}

void modify(int l, int r, int c, int x = 1) {
    if (l <= t[x].l && r >= t[x].r) {
        t[x].cou += c;
        pushup(x); //特别注意, 别忘了修改后pushup!!!
        return;
    }
    int mid = t[x].l + t[x].r >> 1;
    if (l <= mid) modify(l, r, c, x << 1);
    if (r > mid) modify(l, r, c, x << 1 | 1);
    pushup(x);
}
int main()
{
    int n; int T = 1;
    while (scanf("%d", &n), n) {
        v.clear(); line.clear(); v.push_back(-0x3f3f3f3f);
        rep(i, n) {
            double x1, y1, x2, y2; scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            line.push_back({ x1, y1, y2, 1 });
            line.push_back({ x2, y1, y2, -1 });
            v.push_back(y1), v.push_back(y2); //离散化y
        }
        sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
        build(1, v.size() - 2); //因为是对(y轴的线段)建树. 有v.size()-1个点, 则线段应再-1. 
        
        sort(line.begin(), line.end());
        
        double res = 0, last = 0; //last表示上一个x的位置
        for (auto& op : line) {
            res += (op.x - last) * t[1].len; //当前结果累加为△x * y轴覆盖长度
            last = op.x;
            modify(find(op.y1), find(op.y2) - 1, op.c);
        }
        printf("Test case #%d\n", T++);
        printf("Total explored area: %.2f\n\n", res);
    }
    return 0;
}
kuangbin线段树专题点这里!!!

END

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值