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 ...
随机推荐
- Statement对象sql注入漏洞的问题
现在通过mysql以及oracle来测试sql注入 漏洞 mysql中的注释# oracle中的注释为-- 所以注入漏洞就产生了 //登录测试 public void login()throw ...
- C# 低耦合 高内聚
低耦合 loosely Coupling 松散的耦合关系=炮友 couple=夫妻 夫妻=法律约束.家庭.生活.财产.繁衍 炮友:吃喝玩乐,不会产生感情方面的依赖 内聚性 内聚性又称块内联 ...
- POJ 3243
Babystep算法.具体为什么,我也不太明白,好像资料不多. #include <iostream> #include <cstdio> #include <cstri ...
- Android - 使用JD-GUI反编译Android代码
使用JD-GUI反编译Android代码 本文地址: http://blog.csdn.net/caroline_wendy Android程序出现Bug时,须要依据Bug寻找问题出错的地方; 须要使 ...
- 从WAVE头文件里获取压缩方式
在解析WAV文件时.会从头文件里读取TAG值,表示当前文件的压缩格式,如该值为1,表示没有压缩,其他表示不同的压缩方式. 如当中为6时,表示压缩方式是alaw 列表例如以下: TAG值 音频描写叙述 ...
- Android接口和框架学习
Android接口和框架学习 缩写: HAL:HardwareAbstraction Layer.硬件抽象层 CTS:CompatibilityTest Suite,兼容性測试套件 Android让你 ...
- Chrome插件开发新手教程
近期在用百词斩这个站点来学单词,感觉非常不错,就是在回想单词列表的时候仅仅有单词和意思.却没有读音.感觉非常不方便,思来思去,想到了Chrome插件能够胜任这个工作.于是小小的研究了一下. Chrom ...
- 又一次发现Oracle太美之glogin.sql
又一次发现Oracle太美之glogin.sql 刚開始接触Oracle的时候,有时候一登陆一个生产环境.常常会出现以下的情况: [oracle@rh64 app]$ sqlplus / as sys ...
- 网页爬虫框架jsoup介绍
序言:在不知道jsoup框架前,因为项目需求.须要定时抓取其它站点上的内容.便想到用HttpClient方式获取指定站点的内容.这样的方法比較笨,就是通过url请求指定站点.依据指定站点返回文本解析. ...
- gwt学习资料
学习资料: 2 3