hdu1542 矩形面积并(线段树+离散化+扫描线)
题意:
给你n个矩形,输入每个矩形的左上角坐标和右下角坐标。
然后求矩形的总面积。(矩形可能相交)。
题解:
前言:
先说说做这道题的感受:
刚看到这道题顿时就懵逼了,几何 烂的渣渣。后来从网上搜题解。才知道用到线段树+离散化+扫描线。不过这是我第一次接触扫描线,根本不知道什么鬼啊。后来各种博客和论文看了一天才真正理解。
不过一想到网上的博客和论文,就来气。都什么啊,代码注释少的很而且说不明白什么意思,比如线段树怎么存每个节点的数据?为什么这么存?每个节点的数据变量都什么意思?更新的时候怎么更新?意思是啥?都TM没写明白,还得老子自己慢慢悟才想明白到底咋回事。
所以为了让更多的同学明白这道题怎么做,线段树+离散化+扫描线到底是个啥,特此写下这篇博客!
正文:
1、从理论角度如何解这道题
扫描线的用法:
由于画图太麻烦了,直接用别人的图吧...
分割线里面的内容转载自http://hzwer.com/879.html
-------------------------------------------------分割线-------------------------------------------------------------------------------------------------------
顾名思义,扫描法就是用一根想象中的线扫过所有矩形,在写代码的过程中,这根线很重要。方向的话,可以左右扫,也可以上下扫。方法是一样的,这里我用的是由下向上的扫描法。

如上图所示,坐标系内有两个矩形。位置分别由左下角和右上角顶点的坐标来给出。上下扫描法是对x轴建立线段树,矩形与y平行的两条边是没有用的,在这里直接去掉。如下图。

现想象有一条线从最下面的边开始依次向上扫描。线段树用来维护当前覆盖在x轴上的线段的总长度,初始时总长度为0。用ret来保存矩形面积总和,初始时为0。
由下往上扫描,扫描到矩形的底边时将它插入线段树,扫描到矩形的顶边时将底边从线段树中删除。而在代码中实现的方法就是,每条边都有一个flag变量,底边为1,顶边为-1。
用cover变量(通过线段树维护)来表示某x轴坐标区间内是否有边覆盖,初始时全部为0。插入或删除操作直接让cover += flag。当cover > 0 时,该区间一定有边覆盖。
开始扫描到第一条线,将它压入线段树,此时覆盖在x轴上的线段的总长度L为10。计算一下它与下一条将被扫描到的边的距离S(即两条线段的纵坐标之差,该例子里此时为3)。
则 ret += L * S. (例子里增量为10*3=30)
结果如下图
  橙色区域表示已经计算出的面积。
扫描到第二条边,将它压入线段树,计算出此时覆盖在x轴上的边的总长度。
例子里此时L=15。与下一条将被扫描到的边的距离S=2。 ret += 30。 如下图所示。
绿色区域为第二次面积的增量。
接下来扫描到了下方矩形的顶边,从线段树中删除该矩形的底边,并计算接下来面积的增量。如下图。
 蓝色区域为面积的增量。
此时矩形覆盖的总面积已经计算完成。 可以看到,当共有n条底边和顶边时,只需要从下往上扫描n-1条边即可计算出总面积。
此题因为横坐标包含浮点数,因此先离散化。另外,因为用线段树维护的是覆盖在x轴上的边,而边是连续的,并非是一个个断点,因此线段树的每一个叶子结点实际存储的是该点与下一点之间的距离。
--------------------------------------------------分割线-------------------------------------------------------------------------------------------------------------------------------
我的理解:
线段树存的是在x轴上线段区间的覆盖情况。
把矩形的上下边拆分成一条一条的线,每个左右边看成是x轴方向一个一个的点。把这些上下边和点都按升序排序。然后用传说中的扫描线从下往上挨个扫描每条上下边。
当遇到一条边时,在线段树中把这条边覆盖的每个线段打上标记,cover+=flag。(线结构体里的flag变量标识该条线段是上边还是下边,为1是下边,-1是上边),当每个节点的cover==0时,说明这个节点所掌控的线段范围没有被覆盖或没有完全被覆盖,cover>0时,被覆盖。
更新完之后,然后用(即将扫描到的下一条边的高度-当前边的高度)*x轴上线段区间被覆盖的长度,就是当前所计算的面积。然后把这些面积加在一起就是总面积。
由于横坐标范围太大,要离散化之后再在线段树中存储。(离散化自己百度,不难)。
那么线段树怎么存线段呢?比如1~10长度的线段,怎么存,每个节点都有什么意义?看下图:
存1~10每个区间的长度。

为什么这么存呢?看下图:

这样每个编号对应一段单位线段。所以1~3的长度是编号1,2所代表的线段的长度。
这只是一个简单的例子。
明白了这些,来看下代码:
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio> using namespace std; const int MAX=5000; typedef struct node //树节点结构体
{
node(){l=r=len=flag=0;}
int l,r;
double len; //如果该区间下有被覆盖的线段,len表示被覆盖的区域的长度
int flag; //标识是否被覆盖
}TreeNode; typedef struct line //边 结构体,f=1为下边,f=-1为上边
{
line(){a=b=h=f=0;}
double a,b; //a,b分别表示这条线的左右横坐标
double h; //h表示这条线段所在的高度
int f; //标识是上边还是下边
}Line; double a[MAX]; int n;
int line_len; //有line_len条边
int a_len; //有a_len个横坐标
TreeNode tNode[MAX<<4];
Line line[MAX]; void buildTree(int p,int l,int r)
{
tNode[p].l=l;
tNode[p].r=r;
tNode[p].flag=0; //标识是否被覆盖
tNode[p].len=0; //如果该区间下有被覆盖的线段,len表示被覆盖的区域的长度
if(l==r)
return;
int mid=(l+r)>>1;
buildTree(p<<1,l,mid);
buildTree(p<<1|1,mid+1,r);
return;
} void getLen(int p) //计算当前节点被覆盖的长度
{
if(tNode[p].flag) //如果当前节点被完全覆盖
{
tNode[p].len=a[tNode[p].r+1]-a[tNode[p].l]; //每个节点代表的是当前节点到下一个节点的长度
}
else if(tNode[p].l==tNode[p].r) //叶子节点,单位线段,如果当前节点没有被覆盖或者没有被完全覆盖,那么就是没有给覆盖
{
tNode[p].len=0;
}
else
tNode[p].len=tNode[p<<1].len+tNode[p<<1|1].len; //不是叶子节点,不知道覆盖情况,从孩子节点获取信息
} void update(int p,int l,int r,int val) //更新线段树 val为-1表示覆盖一层,否则删除一层
{
if(tNode[p].l>=l&&tNode[p].r<=r) //不必更新到底,因为每条上下边都是对应的,添加删除不影响其他节点,而且所需要的信息是第一个节点的信息。
{
tNode[p].flag+=val;
getLen(p);
return;
}
if(tNode[p].l>r||tNode[p].r<l)
{
return;
}
update(p<<1,l,r,val);
update(p<<1|1,l,r,val);
getLen(p);
return;
} int binary(double key,int low,int high) //二分查找每个横坐标离散化之后所对应的编号。建树的时候要用这些编号建树,不是用实际的坐标
{
while(low<=high)
{
int mid=(low+high)>>1;
if(a[mid]==key)
return mid;
if(key<a[mid])
{
high=mid-1;
}
if(key>a[mid])
{
low=mid+1;
}
}
return -1;
} bool compare(Line a,Line b)
{
return a.h<b.h;
} int main()
{
int n;
int Case=1;
while(cin>>n&&n)
{
line_len=0;
a_len=0;
memset(a,0,sizeof(a));
double x1,y1,x2,y2;
for(int i=0;i<n;i++)
{
cin>>x1>>y1>>x2>>y2; //横坐标的每个点赋值
a[a_len++]=x1;
a[a_len++]=x2; //每条线赋值
line[line_len].a=x1;
line[line_len].b=x2;
line[line_len].h=y1;
line[line_len].f=-1; line[line_len+1].a=x1;
line[line_len+1].b=x2;
line[line_len+1].h=y2;
line[line_len+1].f=1; line_len+=2;
}
sort(a,a+a_len); //把所有横坐标排序
int num=1; for(int i=1;i<a_len;i++) //排除重复的
{
if(a[i]!=a[i-1]) a[num++]=a[i];
} buildTree(1,0,num-1); //排除重复的之后由num个横坐标,所以建树范围是0~num-1,也可以是1~num。随便 sort(line,line+line_len,compare); //所有的边所处高度排序 double sum=0;
for(int i=0;i<line_len-1;i++) //遍历排序完的边
{
update(1,binary(line[i].a,0,num-1),binary(line[i].b,0,num-1)-1,line[i].f); //先更新覆盖情况
sum+=(line[i+1].h-line[i].h)*tNode[1].len; //计算面积
} printf("Test case #%d\n",Case);
printf("Total explored area: %.2lf\n\n",sum);
Case++;
} return 0;
}
先要把这个理解,再看代码,不然看也看不懂,慢慢理解,不能急。
hdu1542 矩形面积并(线段树+离散化+扫描线)的更多相关文章
- POJ1151Atlantis 矩形面积并[线段树 离散化 扫描线]
		
Atlantis Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 21734 Accepted: 8179 Descrip ...
 - POJ 1151Atlantis 矩形面积并[线段树 离散化 扫描线]
		
Atlantis Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 21734 Accepted: 8179 Descrip ...
 - poj-1151矩形面积并-线段树
		
title: poj-1151矩形面积并-线段树 date: 2018-10-30 22:35:11 tags: acm 刷题 categoties: ACM-线段树 概述 线段树问题里的另一个问题, ...
 - 【POJ 2482】 Stars in Your Window(线段树+离散化+扫描线)
		
[POJ 2482] Stars in Your Window(线段树+离散化+扫描线) Time Limit: 1000MS Memory Limit: 65536K Total Submiss ...
 - HDU1542 Atlantis —— 求矩形面积并 线段树 + 扫描线 + 离散化
		
题目链接:https://vjudge.net/problem/HDU-1542 There are several ancient Greek texts that contain descript ...
 - hdu1542线段树+离散化+扫描线
		
参考博客: http://blog.csdn.net/xingyeyongheng/article/details/8927732 总的来说就是用一条(假想的)线段去平行x轴从下往上扫描,扫描的过程中 ...
 - Codeforces 610D Vika and Segments 线段树+离散化+扫描线
		
可以转变成上一题(hdu1542)的形式,把每条线段变成宽为1的矩形,求矩形面积并 要注意的就是转化为右下角的点需要x+1,y-1,画一条线就能看出来了 #include<bits/stdc++ ...
 - POJ 1177/HDU 1828 picture 线段树+离散化+扫描线 轮廓周长计算
		
求n个图矩形放下来,有的重合有些重合一部分有些没重合,求最后总的不规则图型的轮廓长度. 我的做法是对x进行一遍扫描线,再对y做一遍同样的扫描线,相加即可.因为最后的轮廓必定是由不重合的线段长度组成的, ...
 - Picture POJ - 1177  线段树+离散化+扫描线  求交叉图像周长
		
参考 https://www.cnblogs.com/null00/archive/2012/04/22/2464876.html #include <stdio.h> #include ...
 
随机推荐
- 【转】linux 增加用户 useradd 用法小结及配置文件说明
			
原文地址:https://blog.csdn.net/warden2010/article/details/5132290 在Linux 系统中,所有的用户和组像一个国家.如果国家要繁荣昌盛的话,需要 ...
 - JS 100元购物卡,牙刷5元,香皂2元、洗发水15元 100元正好花完有多少种可能
			
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
 - tensorflow常见函数
			
1.类型转换 tf.string_to_number(string_tensor,out_type=None,name=None) #字符串转为数字 tf.to_double(x,name='ToD ...
 - 洛谷P2038 无线网络发射器选址 水题 枚举
			
刚开始边界写错了(将128写成127). 注意n <= 20,所以可以每读入一个点就将其周边更新,这样最多也只会有 40 * 40 * 20 种位置需要被枚举. Code: #include&l ...
 - Quoit Design (HDU 1007)平面的最近点对
			
题目大意:给定平面上的 n 个点,求距离最近的两个点的距离的一半. n <= 10^5. 晕乎乎的度过了一上午... 总之来学习下分治吧233 分治就是把大问题拆成小问题,然后根据对小问题处 ...
 - 自我介绍About me
			
我的github:www.github.com/dcdcbigbig 欢迎来加star!(雾) tm就是个菜逼
 - Vue学习之路第十八篇:私有过滤器的使用
			
1.上篇已经介绍了全局过滤器的使用,“全局”顾名思义就是一次定义处处使用,可以被一个页面里不同的Vue对象所使用,如下代码所示: <body> <div id="app1& ...
 - Flex简易教程
			
常见的前端布局模型涵盖浮动.定位和弹性盒等 CSS 技术,其中浮动和定位技术往往在制作自适应布局页面时显得不够优雅--对于浮动布局,前后端分离时代很多时候我们并不知道每行会遍历显示多少个元素,每个 ...
 - Linux 内核管理
			
Linux内核基础:Linux Kernel: Linux内核的体积结构是单内核的,但充分借鉴了微内核设计体系的优点,为内核引入模块化机制,使得虽然是单内核,但工作在模块化的方式下,并且模块可以动态 ...
 - nginx与tomcat搭建集群,负载均衡
			
--------------------------------------------------- 搭建环境(在桌面上即可完成测试) 先准备2个tomcat服务器 解压tomcat压缩包 得到 把 ...