其他分享
首页 > 其他分享> > 油漆面积(扫描线)

油漆面积(扫描线)

作者:互联网

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