题意:给出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. MATLAB应用专题part2-电力电子仿真技术

    有匪君子,如切如磋,如琢如磨. --<诗经·卫风·淇奥> 这篇博客知识我记录一下我在仿真学习中积累到的知识和遇到的坑. 第一部分:知识部分 1.为什么电阻与电感串联电路中电流的波形比电压的 ...

  2. 【WCF Restful】Post传参示范

    1.传多个参数 接口定义:(ResponseFormat与RequestFormat分别将相应参数序列化.请求参数反序列化) [OperationContract] [WebInvoke(UriTem ...

  3. 开始Python之旅

    2019/11/22,今天是个好日子!非常高兴笔者的博客申请得到通过,让本人有幸成为博客园的一份子! 本人是一枚自然语言处理的爱好者,所用到的语言工具就是python,因此笔者的博客内容主要关乎pyt ...

  4. AT1219 歴史の研究[回滚莫队学习笔记]

    回滚莫队例题. 这题的意思大概是 设 \(cnt_i\) 为 l ~ r 这个区间 \(i\) 出现的次数 求\(m\) 次询问 求 l~r 的 max {\(a_i\) * \(cnt_i\)} \ ...

  5. vulnhub靶机之DC6实战(wordpress+nmap提权)

    0x00环境 dc6靶机下载地址:https://download.vulnhub.com/dc/DC-6.zip dc6以nat模式在vmware上打开 kali2019以nat模式启动,ip地址为 ...

  6. XSY3163

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

  7. Radmin Server v3.5.1 汉化破解绿色版 第四版

    下载:https://pan.baidu.com/s/1skOXffJ 使用方法:1.运行“安装.bat”,安装过程静默,安装后无托盘图标,不创建任何快捷方式.2.运行“设置.bat”,进入 radm ...

  8. UVA1349(带权二分图最大匹配 --> KM算法模板)

    UVA1349 题意:给定一些有向带权边,求出把这些边构造成一个个环,总权值最小 解法: 对于带权的二分图的匹配问题可以用通过KM算法求解. 要求最大权匹配就是初始化g[i][j]为0,直接跑就可以: ...

  9. LeetCode 112. 路径总和 (递归遍历二叉树)

    题目链接:https://leetcode-cn.com/problems/path-sum/ 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 ...

  10. arm的字节对齐问题总结(转)

    问题由来:pc的lsb总是0,因为代码至少要字对齐.cm3的指令至少是半字对齐的(16) 一.啥是字对齐?为啥要字对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访 ...