题意:给出n个矩形的左下角左边和右上角坐标,求这n个矩形的面积并

原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542


典型的扫描线算法的题目

什么是扫描线?

顾名思义,扫描线就是用一根平行于x轴或y轴的线,把所有矩形都扫过去,并在这个过程逐渐把这些矩形的面积并求出来,下图给了平行于y轴的线的扫描情形

那么这条扫描线是怎么实现的呢?

以下讲述扫描线平行于y轴的情形


先说说扫描线的移动问题

第一步,坐标离散化

第二步,开始扫描

这根线顺着离散化后的x坐标挪就行了,这就是扫描线的移动方法

移动问题说完了,我们接下来说说怎么用这条线怎么把面积扫出来

目前这条线只是单纯的移动,并没什么用,也就是说这移动的过程中得附带一些操作,使得这条线有扫面积的功能才行,而这正是扫描线算法的难点。

在说明面积求法之前,先介绍两个概念,矩形的入边与矩形的出边

对于同一个矩形来说,先被扫描线扫到的边为入边,后被扫描线扫到的边为出边

矩形上下的线呢?emmm......既然扫描线是平行y轴去移动的,那么矩形平行于x轴的边就没有用了,所以我们不管它,只看平行于y轴的边即可

为了接下来扫描面积的说明方便,标下坐标轴的值

知道了入边出边的概念,这对求面积有什么用呢?

这涉及到有效覆盖线段的概念,扫描线扫到入边时,代表着那条入边的区间被覆盖,扫描到出边时,代表出边的区间被取消覆盖,注意,这里所说的覆盖是可以叠加的,比如扫到x2时,[y2, y3]就被覆盖了两次

那么扫到x3,x4时情况也就分别如下图所示了

只要覆盖次数大于等于1的区间就是属于有效覆盖区间

那么面积跟这些覆盖区间有什么关系呢?其实是这样的,有效覆盖区间的长度应该就是扫描线所扫面积的宽度,也就是说,把扫描线看成刷子,当前有效的覆盖区间就是刷子下笔的地方,扫描线移动到下个x坐标上时,那整块区域就被刷上了,下面两张图给出从x1刷到x2的情形

看,扫描线从x1挪到x2时,把x1到x2之间该求的面积刷上了,同时由于目前区间有效覆盖区间是[y1, y4],这块地方又成为了下笔的区间,下面直接展示刷完所有矩形的过程

这样刷着刷着,刷完x4的时候全部矩形的并面积也就出来了

就这样,通过与有效覆盖区间的结合,扫描线就具备了求矩形并面积的功能


讲完这些,代码怎么实现呢?

涉及到区间覆盖,我们就用线段树去实现

坐标离散化后,把区间存入线段树中,根结点就是存了区间[y1, yn]

对于上面的例子,我们给出线段树的结构

那么除了区间,我们还需要向结点中添加什么信息呢?

既然涉及到区间是否被覆盖,那么我们就得添加个区间是否被覆盖的标记(这里我给的标记名是res),由于覆盖可以叠加,那么我们这个标记就不能是简单的bool值,而是int值,表示被覆盖了多少次,同时若是扫描线扫到了入边,这个标记就+1,扫到出边就-1

单单区间覆盖还不行,我们还得搞个有效覆盖区间的信息(这里我给的标记名是len),这个信息可以往上push_up,所以查询整个区间的有效覆盖长度时我们只需查询根结点就好,利用线段树的特性使得算法高效

所以,我们扫描线算法的核心就是扫描线在扫描过程中怎么维护线段树的res值跟len值

len值好维护,结点的res值大于等于1的话该节点的len就等于所管区间的长度,否则就是等于两个孩子的len值相加

那么res的值怎么维护呢?叶子结点好处理,只有区间被完全覆盖和完全不被覆盖两种情况,所以res就单纯的加加减减就好,但是树节点存在非完全覆盖的情况的,这就令树节点的res值很难处理了,如果是多个子节点合并着使得树节点所管区间被完全覆盖,那么这合并过程中每次都往子节点询问一次。最坏的情况下每次都得询问到叶子结点,这样线段树的高效性就没法体现出来。

有个解决的办法,那就是res值不要定义成该结点的区间被覆盖几次,而是定义成该结点的区间被直接完全覆盖了几次,什么意思呢?对于上面的线段树,你要是先覆盖[y2, y3],再覆盖[y3, y4],这时候[y2, y3]跟[y3, y4]这两个结点都是被直接覆盖的,它们的res值自然都是1,这时候[y2, y4]虽然时通过[y2, y3]跟[y3, y4]合并着被完全覆盖了,但是这并不属于直接覆盖,所以该结点的res = 0

可是对于一来就覆盖[y1, y3]的情况呢?并没有直接管[y1, y3]的结点啊?我们分析知道在这个线段树结构中不管怎么处理,[y1, y3]必然都要被拆分成[y1, y2]和[y3, y4]来处理,这就有结点来直接管了,也就是说对于[y1, y3]这种没有结点直接管的区间,也是能被拆成有限个区间被线段树的结点直接管辖的

这样做的好处是什么呢?就是为了解决上边的res怎么处理的问题,在这里每个结点的res的值都是独立的,所以处理起来也都是独立的,不存在要从父节点或子节点中获取的情况,而且这个len的处理也没有受到影响,依旧同上面那样的方式去维护即可

线段树构建好了,扫描线的扫法也知道了,那么我们接下来就只剩最后一步,就是把扫描线跟线段树揉和在一起,实际上这也很简单,扫描线当前覆盖了哪些边哪些信息,我们就把它们读入线段树即可


下面就贴AC代码了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std; const int Maxn = ; struct Edge {//平行于y轴的边
double l, r;//边所覆盖区间
double x;//边在x轴上的坐标
int d;//该值为1或-1,表示入边或出边 Edge(double l, double r, double x, int d) : l(l), r(r), x(x), d(d) {} bool operator < (const Edge& B) const {
return x < B.x;
}
}; struct Node { //线段树的结点
int l, r; //结点所管辖区间
int res; //区间被直接完全覆盖次数
double len; //结点所管辖区间的有效覆盖长度
}; vector<double> x;
vector<double> y; vector<Edge> e;
Node Tree[Maxn << ]; int getX(double X)
{
return lower_bound(x.begin(), x.end(), X) - x.begin();
} int getY(double Y)
{
return lower_bound(y.begin(), y.end(), Y) - y.begin();
} inline int lc(int p) {
return p << ;
} inline int rc(int p) {
return p << | ;
} void push_up(int p)
{
if (Tree[p].res > ) Tree[p].len = Tree[p].len = y[Tree[p].r] - y[Tree[p].l]; //该区间线段已被直接覆盖,直接取其有效长度
else if (Tree[p].l + == Tree[p].r) Tree[p].len = ; //是叶子节点,则其有效长度为0
else Tree[p].len = Tree[lc(p)].len + Tree[rc(p)].len;//不是被直接覆盖,就从子节点提取有效长度值
} void Build_Tree(int l, int r, int p)
{
Tree[p].l = l;
Tree[p].r = r; if (l + == r) return; int m = l + r >> ;
Build_Tree(l, m, lc(p));
Build_Tree(m, r, rc(p));
} void update(int L, int R, int p, int d)
{
if (Tree[p].r <= L || Tree[p].l >= R) return;
if (L <= Tree[p].l && Tree[p].r <= R) {
Tree[p].res += d;
push_up(p);
return;
} update(L, R, lc(p), d);
update(L, R, rc(p), d);
push_up(p);
} double Query()
{
return Tree[].len;
} void Test()
{
for (int i = ; i < y.size(); i++) {
printf("%d: %f\n", i, y[i]);
}
} void Ini()
{
e.clear();
x.clear();
y.clear();
memset(Tree, , sizeof(Tree));
} void Input(int n)
{
x.push_back(-); //使离散化的下标从1开始,喜欢从0开始的同学可以忽略
y.push_back(-);
for (int i = ; i <= n; i++) {
double x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
x.push_back(x1);
x.push_back(x2);
y.push_back(y1);
y.push_back(y2); e.push_back(Edge(y1, y2, x1, ));
e.push_back(Edge(y1, y2, x2, -));
}
e.push_back(Edge(, , + , ));//末尾添加个0值边,方便循环处理
} void solve(int Case)
{
cout << "Test case #" << Case << endl;
sort(x.begin(), x.end());
sort(y.begin(), y.end());
sort(e.begin(), e.end()); x.erase(unique(x.begin(), x.end()), x.end());
y.erase(unique(y.begin(), y.end()), y.end()); Build_Tree(, y.size() - , ); double ans = ;
int cur = ;
for (int i = ; i < x.size()-; i++) {
while (e[cur].x == x[i]) {
update(getY(e[cur].l), getY(e[cur].r), , e[cur].d);
cur++;
} ans += Query() * (x[i + ] - x[i]);
} printf("Total explored area: %.2f\n", ans);
} int main()
{
int Case = ;
int n;
while (cin >> n && n) {
Case++;
Ini();
Input(n);
solve(Case);
cout << endl;
} return ;
}

HDU 1542 Atlantis(扫描线算法)的更多相关文章

  1. (HDU 1542) Atlantis 矩形面积并——扫描线

    n个矩形,可以重叠,求面积并. n<=100: 暴力模拟扫描线.模拟赛大水题.(n^2) 甚至网上一种“分块”:分成n^2块,每一块看是否属于一个矩形. 甚至这个题就可以这么做. n<=1 ...

  2. HDU 1542 Atlantis(矩形面积并)

    HDU 1542 Atlantis 题目链接 题意:给定一些矩形,求面积并 思路:利用扫描线,因为这题矩形个数不多,直接暴力扫就能够了.假设数据大.就要用线段树 代码: #include <cs ...

  3. HDU 1542 Atlantis(线段树扫描线+离散化求面积的并)

    Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  4. HDU 1542 - Atlantis - [线段树+扫描线]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542 Time Limit: 2000/1000 MS (Java/Others) Memory Li ...

  5. hdu 1542 Atlantis(线段树,扫描线)

    Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  6. hdu 1542 Atlantis(段树&amp;扫描线&amp;面积和)

    Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  7. hdu 1542 Atlantis 段树区,并寻求,,,尼玛真坑人数据,不要打开一小阵!

    Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  8. (中等) HDU 1542 Atlantis,扫描线。

    Problem Description There are several ancient Greek texts that contain descriptions of the fabled is ...

  9. HDU 1542 Atlantis(线段树面积并)

     描述 There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. S ...

随机推荐

  1. Git命令行操作方法

    1.GitHub上创建一个Repositories(仓库),并复制仓库地址

  2. 关于在Spring项目中使用thymeleaf报Exception parsing document错误

    今天在使用SpringBoot的过程中,SpringBoot版本为1.5.18.RELEASE,访问thymeleaf引擎的html页面报错Exception parsing document: 这是 ...

  3. MySQL基础(1) | 数据类型

    MySQL基础(1) | 数据类型 数值类型 TINYINT #小整数值,1 字节,有符号(-128,127),无符号(0,255) SMALLINT #大整数值,2 字节 MEDIUMINT #大整 ...

  4. Linux学习Day1:开班第一天

    其实这篇博客应该昨天就要写完的,算是补作业吧. 昨天(2020年2月14日)是参加Linux线上培训的第一天,当天培训结束后,老师要求学员每天写一篇博客来记录自己学到的知识,于是就有了这篇博客的诞生. ...

  5. 剑指offer-面试题58_1-翻转单词顺序-字符串

    /* 题目: 输入一个英文句子,翻转单词顺序,但单词内部顺序不变. */ /* 思路: 先翻转整个句子,再将每个单词分别翻转一次. */ #include<iostream> #inclu ...

  6. USB-Blaster CPLD FPGA Intel 驱动安装不上的问题,文件的哈希值不在指定的目录文件中,的解决办法,其实很简单

    intel的官网的驱动安装文档: https://www.intel.com/content/www/us/en/programmable/support/support-resources/down ...

  7. 让我们纯手写一个js继承吧

    继承在前端逻辑操作中是比较常见的,今天我们就从零开始写一个js的继承方式 在es5中继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上Parent.call(this),在es6中则 ...

  8. nvm Nodejs 版本管理器 安装及配置

    1.如果已安装nodejs请先卸载干净 nodejs:删除C:\Program Files\nodejs 文件夹 npm:删除C:\Users\{用户名}\AppData\Roaming\npm 文件 ...

  9. XSY3163

    题意 \(n\)阶无向图,带边权,边有黑白两色,问有多少棵白边恰好为\(k\)的树,边权最小 做法 先二分出给白边的附加权值,然后矩阵树讨论同权值块即可 题外话 乍一看好神仙,然后..

  10. H5_0023:html页面禁止放大缩小页面

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scal ...