题意:

  给你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 矩形面积并(线段树+离散化+扫描线)的更多相关文章

  1. POJ1151Atlantis 矩形面积并[线段树 离散化 扫描线]

    Atlantis Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 21734   Accepted: 8179 Descrip ...

  2. POJ 1151Atlantis 矩形面积并[线段树 离散化 扫描线]

    Atlantis Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 21734   Accepted: 8179 Descrip ...

  3. poj-1151矩形面积并-线段树

    title: poj-1151矩形面积并-线段树 date: 2018-10-30 22:35:11 tags: acm 刷题 categoties: ACM-线段树 概述 线段树问题里的另一个问题, ...

  4. 【POJ 2482】 Stars in Your Window(线段树+离散化+扫描线)

    [POJ 2482] Stars in Your Window(线段树+离散化+扫描线) Time Limit: 1000MS   Memory Limit: 65536K Total Submiss ...

  5. HDU1542 Atlantis —— 求矩形面积并 线段树 + 扫描线 + 离散化

    题目链接:https://vjudge.net/problem/HDU-1542 There are several ancient Greek texts that contain descript ...

  6. hdu1542线段树+离散化+扫描线

    参考博客: http://blog.csdn.net/xingyeyongheng/article/details/8927732 总的来说就是用一条(假想的)线段去平行x轴从下往上扫描,扫描的过程中 ...

  7. Codeforces 610D Vika and Segments 线段树+离散化+扫描线

    可以转变成上一题(hdu1542)的形式,把每条线段变成宽为1的矩形,求矩形面积并 要注意的就是转化为右下角的点需要x+1,y-1,画一条线就能看出来了 #include<bits/stdc++ ...

  8. POJ 1177/HDU 1828 picture 线段树+离散化+扫描线 轮廓周长计算

    求n个图矩形放下来,有的重合有些重合一部分有些没重合,求最后总的不规则图型的轮廓长度. 我的做法是对x进行一遍扫描线,再对y做一遍同样的扫描线,相加即可.因为最后的轮廓必定是由不重合的线段长度组成的, ...

  9. Picture POJ - 1177 线段树+离散化+扫描线 求交叉图像周长

    参考  https://www.cnblogs.com/null00/archive/2012/04/22/2464876.html #include <stdio.h> #include ...

随机推荐

  1. day06-08面向对象进阶

    isinstance和issubclass 反射 setattr delattr getattr hasattr __str__和__repr__ __del__ item系列 __getitem__ ...

  2. Java解析导入Excel文件后台代码实现

    使用MultipartFile上传Excel文件后端代码实现:(springmvc下的spring-webmvc (MultipartFile )上传) 由于POST一个包含文件上传的Form会以mu ...

  3. 取/etc/password文件最后一个单词的最后一个字符

    三种方法都可以 [root@localhost ~]# sed -n "1,5 s#.*\(.\)#\1#p" /etc/passwd [root@localhost ~]# se ...

  4. python二级登陆菜单

    """ 1.三级菜单 注册 登陆 注销 2.进入每一个一级菜单,都会有下一级的菜单"""user_item = dict()try: whi ...

  5. Linux设备驱动--块设备(一)之概念和框架(转)

    基本概念   块设备(blockdevice) --- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性 ...

  6. 利用Tensorflow实现手写字符识别

    模式识别领域应用机器学习的场景非常多,手写识别就是其中一种,最简单的数字识别是一个多类分类问题,我们借这个多类分类问题来介绍一下google最新开源的tensorflow框架,后面深度学习的内容都会基 ...

  7. poj 1611 简单并查集的应用

    #include<stdio.h> #define N 31000 int pre[N]; int find(int x) { if(x!=pre[x])     pre[x]=find( ...

  8. Linux C++服务器端进程SDK

    目标:开发人员在这个SDK的基础上开发,不用关注技术细节,只需要开发对应业务消息处理函数即可 1.每个进程SDK包括一个系统服务和多个业务服务,对外发布服务ID,每个服务有一个请求队列和一个反馈队列, ...

  9. spark视频教程免费下载

    下载地址:点我下载   其它章节陆续上传中,Hadoop视频教程正在整理中,敬请关注.

  10. hdu 1166 敌兵布阵——(区间和)树状数组/线段树

    pid=1166">here:http://acm.hdu.edu.cn/showproblem.php?pid=1166 Input 第一行一个整数T.表示有T组数据. 每组数据第一 ...