多个矩形,求覆盖面积,周长,及交点
问题:给出若干个矩形,(给的是矩形左上角和右下角坐标),求最后所得图形的面积/周长;
三个矩形如左图所示,而若要计算面积,看右图,用3个矩形各自的面积之和减去重复部分(红色和蓝色)的面积
人算很简单,但是用算法怎么实现呢?
此类问题一般都是用线段树辅助扫描法来计算;
什么是扫描法?有什么用?怎么用?
可以想象成一根假想的线,将图从左往右或从右往左或自下而上或自上而下“扫描”一遍,至于扫描的是什么则根据具体应用选择。
扫描线可以计算矩形面积、周长,可以计算线段交点,可以实现多边形扫描转换,在图的处理方面经常用到。
这里总结一下扫描线计算矩形面积和周长的算法。
怎么用?首先,对于之前的图,除了用总面积减去重合面积,还可以换一种计算方法,如图:
此图用4条横线将整个图划分成了5个部分,显然此时再算面积就可以用各个颜色的部分求和。
想想,这样计算的整个慢过程:
假设我们的视线自下而上,首先,我们看到了最下面灰色矩形的下边,
用这个下边的长度乘以这条边和上一条边的高度差即得到灰色矩形面积,
继续看到蓝色的矩形的下边,虽然蓝色矩形有两个,但我们计算时自然会用结合律将两个矩形的下边加起来再去乘以同样的高,
然后重复这样的操作,我们最终可以求得整个图形的面积。
但是,这依旧是人做的,计算机要怎么实现呢?
首先的问题是,计算机要怎么保存这张图这些矩形?
从刚才的过程,我们不难发现,我们只需要保存这张图里面的所有水平的边即可。
对于每条边,它所拥有的属性是:这条边的左右端点(的横坐标),这条边的高度(纵坐标),这条边属于矩形的上边还是下边(想想为什么保存这个属性)
刚刚计算中我们遇到两个蓝色矩形的一部分一眼就能看出这两个蓝色矩形的‘宽’是多少,用计算机怎么做到?
线段树华丽登场!
我们以整个图最左边的竖线作为区间左端点,最右边的竖线作为区间右端点,去维护这个区间的有效长度(即被覆盖的长度)
比如扫到第2条边的时候,有效长度就是两个蓝色矩形的宽之和。
这样,我们用扫描线去扫描每一条边的时候,都需要更新线段树的有效长度
是如何更新的呢?
如果扫到的这条边是某矩形的下边,则往区间插入这条线段
如果扫到的这条边是某矩形的上边,则往区间删除这条线段
为什么?自己试着模拟一下就不难发现:
因为我们是自下而上的扫这个图,扫到下边相当于刚刚进入一个矩形,扫到上边则是要离开一个矩形
利用线段树把每条边的有效长度找到了,也就是找到了每部分的所有矩形的总宽,那么高呢?
高就简单多了,对于所有的边,按照高度从小到大排列,那么矩形高就是每相邻边之间的高度差
给个例子:HDU 1542 Atlantis
然后看看用代码具体是怎么实现的:
ps: 特别说一下,关于上边和下边的标记,用-1标记下边,1标记上边是最合理的(想想为什么,提示:下边--删除,上边--插入)
这题横坐标略大,需要离散化处理
#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
const int N = 111;
struct Edge
{double l,r;//这条线的左右端点的横坐标double h;//这条线的纵坐标int f;//这条线是矩形的上边还是下边
}e[N<<1];
bool cmp(Edge a,Edge b)
{return a.h < b.h;
}
struct Node
{int l,r;//横坐标的区间,是横坐标数组的下标int s;//该节点被覆盖的情况(是否完全覆盖)double len;//该区间被覆盖的总长度
}q[N*8];
double x[2*N];//横坐标
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void build(int i,int l,int r)
{q[i].l = l,q[i].r = r;q[i].s = 0;q[i].len = 0;if (l == r) return;int mid = m(i);build(ls,l,mid);build(rs,mid+1,r);
}
void pushup(int i)
{if (q[i].s) //非零,已经被整段覆盖{q[i].len = x[q[i].r+1] - x[q[i].l];}else if (q[i].l == q[i].r) //这是一个点而不是线段{q[i].len = 0;}else //是一条没有整个区间被覆盖的线段,合并左右子的信息{q[i].len = q[ls].len + q[rs].len;}
}
void update(int i,int l,int r,int xx)//这里深刻体会为什么令下边为1,上边-1
{ //下边插入边,上边删除边if (q[i].l == l&&q[i].r == r){q[i].s += xx;pushup(i);//更新区间被覆盖de总长度return;}int mid = m(i);if (r <= mid) update(ls,l,r,xx);else if (l > mid) update(rs,l,r,xx);else{update(ls,l,mid,xx);update(rs,mid+1,r,xx);}pushup(i);
}
int main()
{int n;int kas = 0;while (scanf("%d",&n) == 1&&n){int tot = 0;for (int i = 0;i < n;++i){double x1,x2,y1,y2;scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);//输入一个矩形Edge &t1 = e[tot];Edge &t2 = e[1+tot];t1.l = t2.l = x1,t1.r = t2.r = x2;t1.h = y1;t1.f = 1;t2.h = y2;t2.f = -1;x[tot] = x1;x[tot+1] = x2;tot += 2;}sort(e,e+tot,cmp);//边按高度从小到大排序(自下而上扫描)sort(x,x+tot);//离散化横坐标int k = 1;for (int i = 1;i < tot;++i){if (x[i] != x[i-1]) //去重{x[k++] = x[i];}}build(1,0,k-1);//离散化后的区间是[0,k-1]double ans = 0.0;for (int i = 0;i < tot;++i){//因为线段树维护的是横坐标们的下标,所以对每条边求出其两个横坐标对应的下标int l = lower_bound(x,x+k,e[i].l) - x;//在横坐标数组里找到这条边的位置int r = lower_bound(x,x+k,e[i].r) - x - 1;update(1,l,r,e[i].f);//每扫到一条边就更新横向的覆盖lenans += (e[i+1].h - e[i].h)*q[1].len;//q[1]是整个区间,q[1].k=len是整个区间的有效长度//计算面积就是用区间横向的有效长度乘以两条边的高度差(面积是两条边里面的部分)}printf("Test case #%d\n",++kas);printf("Total explored area: %.2f\n\n",ans);}return 0;
}
说完了矩形面积,矩形周长的方法自然是类似的,但是周长的计算却更复杂些,看这张图:
周长可以分成两部分计算,横线和竖线,如图将所有彩色的横线加起来就是横向的所有长度了
然后可以采用竖直方向的扫描线将竖线的所有长度求出来
那么怎么计算横线的长度呢?
横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
想想为什么要加绝对值(提示:下边--删除,上边--插入)
这样用自下而上和从左往右的两次扫描即可得到答案。
但是,这样的方法显得有些笨,有没有更高端的方法呢?
再看一张图:
看出什么了吗?这张图在上面那张图的基础上多了几条竖线。
我的意思是说,我们可以只做一次自下而上的扫描就把横线竖线都算出来!
竖线的算法和上面说的方法一样:【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
竖线要怎么计算?
首先我们现在改一下线段树保存的属性,我们用如下信息记录线段树的节点:
1. l , r : 该节点代表的线段的左右端点坐标
2.len : 这个区间被覆盖的长度(即计算时的有效长度)
3.s : 表示这个区间被覆盖了几次
4. lc , rc : 标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)
5.num :这个区间有多少条线段(这个区间被多少条线段覆盖)
这里的num涉及到竖线的计算,故解释一下,举几个例子:
若区间[0,10]被[1,2][4,5]覆盖,则num = 2
若区间[0,10]被[1,3][4,5]覆盖,则num = 1(两区间刚好连在一起)
若区间[0,10]被[1,5][2,6]覆盖,则num = 1(两区间连起来还是一段)
然后就可以计算竖线了:
竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*2*num
乘2是因为每条线段有两个端点;
看上图中棕色线段的竖线有4条,因为棕色的横线由2条线段组成
白色线段的竖线只有2条,因为白色的横线由1条线段组成(虽然这1条线段是由许多线段组合而成,但它依旧只算1条线段)
这样,依旧只扫一次就可以算出周长。
给个例子:HDU 1828Picture
代码实现:
#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
const int N = 5007;
const int X = 20007;
const int inf = 1<<29;
struct Edge//扫描线
{int l,r;//左右端点的横坐标int h;//这条线的高度,即纵坐标int f;//标记这条边是上边(-1)还是下边(1)
}e[N*2];
bool cmp(Edge a,Edge b)
{return a.h < b.h;//高度从小到大排,扫描线自下而上扫
}
struct Node
{int l,r;//该节点代表的线段的左右端点坐标int len;//这个区间被覆盖的长度int s;//表示这个区间被重复覆盖了几次bool lc,rc;//表示这个节点左右两个端点是否被覆盖(0表示没有被覆盖,1表示有被覆盖)int num;//这个区间有多少条线段(这个区间被多少条线段覆盖)//len用来计算横线 num用来计算竖线
}q[4*X];
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void pushup(int i)//区间合并
{if (q[i].s)//整个区间被覆盖{q[i].len = q[i].r - q[i].l + 1;q[i].lc = q[i].rc = 1;q[i].num = 1;}else if (q[i].l == q[i].r)//这是一个点而不是一条线段{q[i].len = 0;q[i].lc = q[i].rc = 0;q[i].num = 0;}else //是一条没有整个区间被覆盖的线段,合并左右子的信息{q[i].len = q[ls].len + q[rs].len ;//长度之和q[i].lc = q[ls].lc;q[i].rc = q[rs].rc;//和左儿子共左端点,和右儿子共右端点q[i].num = q[ls].num + q[rs].num - (q[ls].rc&q[rs].lc);//如果左子的右端点和右子的左端点都被覆盖了}
}
void build (int i,int l,int r)
{q[i].l = l,q[i].r = r;q[i].s = q[i].len = 0;q[i].lc = q[i].rc = q[i].num = 0;if (l == r) return;int mid = m(i);build(ls,l,mid);build(rs,mid+1,r);
}
void update(int i,int l,int r,int xx)
{if (l == q[i].l && q[i].r == r){q[i].s += xx;pushup(i);return;}int mid = m(i);if (r <= mid) update(ls,l,r,xx);else if (l > mid) update(rs,l,r,xx);else{update(ls,l,mid,xx);update(rs,mid+1,r,xx);}pushup(i);
}
int main()
{int n;while (cin>>n){int x1,x2,y1,y2,mx = -inf,mn = inf;int tot = 0;for (int i = 0;i < n;++i){scanf("%d %d %d %d",&x1,&y1,&x2,&y2);mx = max(mx,max(x1,x2));mn = min(mn,min(x1,x2));Edge & t1 = e[tot];Edge & t2 = e[tot+1];t1.l = t2.l = x1,t1.r = t2.r = x2;t1.h = y1;t1.f = 1;t2.h = y2;t2.f = -1;tot += 2;}sort(e,e+tot,cmp);//数据小可以不离散化int ans = 0;//计算周长int last = 0;//保存上一次的总区间的被覆盖的长度build(1,mn,mx-1);//每两条横线之间才会有竖线for (int i = 0;i < tot;++i){update(1,e[i].l,e[i].r-1,e[i].f);//根据扫描线更新//计算周长//横线:现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值ans += abs(q[1].len - last);//竖线:[下一条横线的高度-现在这条横线的高度]*2*numans += (e[i+1].h - e[i].h)*2*q[1].num;last = q[1].len;//每次都要更新上一次总区间覆盖的长度}printf("%d\n",ans);}return 0;
}
进阶:
HDU 1255 覆盖的面积(线段树+扫描线求面积【升级版】)
转载于:https://www.cnblogs.com/bytebull/p/5973485.html
多个矩形,求覆盖面积,周长,及交点相关推荐
- C#使用多态求方形面积周长和圆的面积周长
class class1{public static void Main(string[] args){//使用多态求矩形面积与周长和圆的面积与周长Shape cl = new Circle(5);d ...
- c#求三角形面积周长公式_C# 定积分求周长面积原理 代码实现
前言: 前些日子,因为工作原因,接触到了求解曲线周长,真的是搞了很久,学生时代真的很简单,但是如今的我来说,忘记了....很多人跟我应该一样. 所以来巩固加强一下记忆.一开始的时候,求周长嘛,找公式呗 ...
- c#求三角形面积周长公式_此题求三角形的面积,多数学生完全没思路,解题关键是用该知识点...
大家好,今天是2020年7月27日星期一!数学世界继续给大家分享小学数学思考题,这道题要求的是三角形的面积,有一定的难度,仍属于能力提升题,但所用知识全部是学生应该掌握的内容.如果你是刚刚来到这里的新 ...
- c#求三角形面积周长公式_此题要求三角形的面积,但是无法用公式求出,而是通过方程解决...
今天,数学世界给大家分享一道初中数学几何题,这道题的难度并不大,解决此题的关键是要理解同高不同底的两个三角形的面积比等于它们的底长之比,并要灵活运用三角形的面积公式,以及解方程组的知识.下面,我们就一 ...
- C语言 有两个矩形 求重叠面积,计算两个矩形重叠面积的简单方法
实验需要,需要计算两个矩形重叠面积 想来想去觉得挺复杂,搜了下,看见一个超给力的方法 这里分享下: function D = DecideOberlap(Reframe,GTframe) x1 = R ...
- c#求三角形面积周长公式_C#源代码—三角形面积、圆的面积
三角形面积.圆的面积 using System; using System.Collections.Generic; using System.Linq; using System.Text; nam ...
- 封装一个抽象类 Shape,其中包括有求形状面积的抽象方法getArea()和求 形状周长的非抽象方法getPerimeter()。
软件NetBeans IDE 7.0.1,需要单独写主类. 封装一个抽象类 Shape,其中包括有求形状面积的抽象方法getArea()和求 形状周长的非抽象方法getPerimeter().继承该抽 ...
- matlab 求圆的周长和面积
求圆的周长和面积 clc; clear; radius=3; c=2*pi*radius; s=pi*radius*radius; c,s 运行结果c = 18.8495559215388 s = 2 ...
- 简单的C++程序求圆的周长和面积
C++程序求圆的周长和面积 求圆的周长和面积 方法1:用结构化方法编程,求圆的周长和面积 方法2:用面向对象方法编程,求圆的周长和面积 初学者易犯错误模型 求圆的周长和面积 数据描述: 半径,周长,面 ...
最新文章
- 吸尘车-真空吸尘车:真空吸尘车
- 让 gRPC 提供 REST 服务
- 微软全新Chromium版Edge浏览器下载
- KeyMob移动广告聚合平台-致力于打造最牛的聚合平台
- Linux(CentOS7.1)修改默认yum源为国内的阿里云yum源
- Android系统启动流程源码分析
- nc65 单据非向导开发 源代码_【免费毕设】ASP.NETIT产品网上物流管理信息系统的设计与实现(源代码+论文)...
- 如何保证测试的覆盖率
- 无需拆机,Kindle 全系列 5.12.2.2 ~ 5.14.2版本如何越狱?如何安装第三方插件
- JavaEye中导入Csdn博客问题
- 金蝶K3-航天信息税控发票开票软件接口程序
- Xshell的安装及使用超详细教程
- 智齿调用a标签时触发绑定事件
- 项目经理的文档提升能力
- 国内首部创业纪实电影《燃点》进校园:如何靠近成功的2%...
- 陌生人社交已成主流,“灵魂”社交软件Soul的上市之忧
- HDLBits刷题Day6
- c#qq群 群号:11069698 欢迎喜欢和爱好c#的朋友加入!
- win10怎么关闭443端口(超详细)
- 本人亲自整理的极客时间设计模式之美下部的硬核笔记(残缺版)最近加班太多,搞不了太多,只能尽量了xd们