ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

油漆面积(扫描线)

2022-01-30 11:02:00  阅读:190  来源: 互联网

标签:include 覆盖 线段 面积 油漆 扫描线 区间 矩形


X星球的一批考古机器人正在一片废墟上考古。

该区域的地面坚硬如石、平整如镜。

管理人员为方便,建立了标准的直角坐标系。

每个机器人都各有特长、身怀绝技。

它们感兴趣的内容也不相同。

经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。

矩形的表示格式为 (x1,y1,x2,y2),代表矩形的两个对角点坐标。

为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。

小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。

其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。

注意,各个矩形间可能重叠。

输入格式

第一行,一个整数 n,表示有多少个矩形。

接下来的 n 行,每行有 4 个整数 x1,y1,x2,y2,空格分开,表示矩形的两个对角顶点坐标。

输出格式

一行一个整数,表示矩形覆盖的总面积。

数据范围

1≤n≤10000,
0≤x1,x2,y2,y2≤10000
数据保证 x1<x2 且 y1<y2。

输入样例1:

3
1 5 10 10
3 1 20 20
2 7 15 17

输出样例1:

340

输入样例2:

3
5 2 10 6
2 7 12 10
8 1 15 15

输出样例2:

128

 这道题目是一道扫描线的模板题,借着这道题来写一下我对扫描线的理解:

先来举个例子:

先看一下怎么求这个图中三个矩形的面积,用数学方法求的话想必大家都会,就是h1*(x2-x1)+h2*(x3-x2)+h3*(x4-x3)+h4*(x5-x4)+h5*(x6-x5),就是几个不重合的小矩形的面积的和,而我们扫描线的原理就是这样的,画这个图大家都能看出来,我们是把每条与y轴平行的边单独考虑,所以我们存每条边只需要存储该边的1个横坐标(始点和终点横坐标相同)和2个纵坐标(下边界和上边界),我们每次处理相邻的两条边,但是前提是我们把所有边都按照横坐标从小到大的顺序已经排完序了,那么我们每次记录答案时只需要加入当前两条边形成的矩形的面积即可,但是问题来了,这个矩形的底边长度比较好求,就是相邻两条边的横坐标之差,关键是我们如何求取这个矩形的纵坐标,我们可以按照y轴建立一颗线段树,存储当前扫描线所覆盖的最大长度,这样的话我们就可以用当前扫描线所覆盖的长度去乘以相邻两条边的横坐标之差,这样就得到当前矩形的面积了。

下面来说一下建造线段树的一些细节:

需要注意的一点是我们需要求的是被覆盖区间的最大长度,而不是被覆盖区间上所有数的和,因为可能存在一段区间是多个矩形始边的交集,所以我们还需要记录一个cnt[],代表当前区间被覆盖了多少次。当我们遇到一个始边时,就把完全在[ymin,ymax]这个区间内的线段树区间覆盖次数数+1(一定要好好理解这句话),而当我们遇到一个终边时,就把完全在[ymin,ymax]这个区间内的线段树区间覆盖次数数-1,所以我们存储边时应该把始边和终边区分开来,我们可以在存储边的结构体中加一个k,k=1表示当前边是始边,k=-1代表当前边是终边。这样的话就能正常进行了,需要注意的一点是一个矩形的左右两条边所对应的y是相同的,而k是相反的,所以我们没必要像一般线段树区间修改那样通过父节点向下更新子节点,假如我们有一段区间[l,r]被覆盖了一次,那么当遍历到当前矩形终边时,这段区间的覆盖次数还会被减少1的。其他就类似于普通的线段树区间修改了,最后一个需要注意的点就是我们对线段树中的点进行修改,而求长度是两点之差再减1,细节比较多,好好看看代码理解一下:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=40003;
int n,l[N],r[N],cnt[N],len[N];//cnt[i]记录第i个区间被覆盖的次数,len[i]记录第i个区间被覆盖的长度
struct edge{
	int x,yn,yx,k;//x记录边横坐标,yn和yx分别记录边下边界和上边界,k=1/-1代表添加/删除此边
}p[N];
bool cmp(edge a,edge b)
{
	return a.x<b.x;
}
void pushup(int id)
{
	if(cnt[id]) len[id]=r[id]-l[id]+1;//若当前区间被覆盖,则该区间被覆盖的长度就是该区间的长度 
	else if(l[id]==r[id]) len[id]=0;//若当前区间是一个点,又因为该区间未被完全覆盖,所以该区间被覆盖的长度为0 
	else len[id]=len[id<<1]+len[id<<1|1];//否则该区间的被覆盖长度就是两个子区间的覆盖长度之和 
}
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;cnt[id]=len[id]=0;
	if(L==R) return ;
	int mid=L+R>>1;
	build(id<<1,L,mid);
	build(id<<1|1,mid+1,R);
} 
void update_interval(int id,int L,int R,int k)
{
	if(l[id]>=L&&r[id]<=R)//当前区间完全在目标区间中 
	{
		cnt[id]+=k;
		pushup(id);
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(mid>=L) update_interval(id<<1,L,R,k);
	if(mid+1<=R) update_interval(id<<1|1,L,R,k);
	pushup(id);
}
int main()
{
	cin>>n;
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		int x1,y1,x2,y2;
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		y1++;y2++;//让线段数从1开始 
		p[++cnt]={x1,y1,y2,1};
		p[++cnt]={x2,y1,y2,-1};
	}
	sort(p+1,p+cnt+1,cmp);
	build(1,1,10002);
	int ans=0;
	for(int i=1;i<=cnt;i++)
	{
		if(i>1) ans+=len[1]*(p[i].x-p[i-1].x);
		update_interval(1,p[i].yn,p[i].yx-1,p[i].k);
	}
	printf("%d",ans);
	return 0;
}

标签:include,覆盖,线段,面积,油漆,扫描线,区间,矩形
来源: https://blog.csdn.net/AC__dream/article/details/122746445

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有