(HDU - 1255)覆盖的面积(扫描线)

给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.

Input

左下角坐标和右上角坐标

输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1<=N<=1000),代表矩形的数量,然后是N行数据,每一行包含四个浮点数,代表平面上的一个矩形的左下角坐标和右上角坐标,矩形的上下边和X轴平行,左右边和Y轴平行.坐标的范围从0到100000. 
注意:本题的输入数据较多,推荐使用scanf读入数据. 

Output

对于每组测试数据,请计算出被这些矩形覆盖过至少两次的区域的面积.结果保留两位小数. 
 

Sample Input

2
5
1 1 4 2
1 3 3 7
2 1.5 5 4.5
3.5 1.25 7.5 4
6 3 10 7
3
0 0 1 1
1 0 2 1
2 0 3 1

Sample Output

7.63
0.00

 这道题依旧是用扫描线来解决的,如果不懂扫描线原理的小伙伴可以看下我之前的博客,下面附上扫描线博客地址:油漆面积(扫描线)_AC__dream的博客-CSDN博客

油漆面积这道题目呢是求矩形并集的面积,而这道题目是求矩形交集的面积,原理呢基本是相同的,油漆面积那道题目中我们维护的是覆盖一段区间至少一次的最大长度,而这一道题显然只维护这一个是不行的,仔细想想容易想到这道题目需要我们维护覆盖一段区间至少两次的最大长度,因为面积本身就是线段的积分,当线段只被覆盖一次的话,那么这个积分出来的面积也就只会被覆盖一次,而当线段被覆盖多次的话,那么这个积分出来的面积也就就会被覆盖多次,说到这,这道题目的思路也就比较清晰了,下面我来说一下如何计算区间中被覆盖两次的最大长度。

先来回顾一下被覆盖至少一次的最大长度怎么求:(需要再提醒一次的是点区间[l,r]表示的是第l个点和第r+1个点之间的距离

首先,如果当前区间整体被覆盖一次,那么显然这个区间被覆盖至少一次的最大长度就是区间本身长度,如果当前区间没有被覆盖一次,这时候分两种清况,一种是当前区间是一个点,那么实际表示的长度就是1,那么可以肯定的是这个长度为一的区间被覆盖至少一次的最大长度就是0,而当这个区间是一个有长度且长度不为1的区间,那么这个区间被覆盖至少一次的最大长度就是该区间的左右两个子区间的至少被覆盖一次的区间最大长度和。下面是该部分代码:

void pushup(int id)
{
    if(cnt[id])     len[id]=alls[r[id]]-alls[l[id]-1];//点区间[l[id],r[id]]实际表示的是线段区间[alls[l[id]-1],alls[r[id]]] 
    else if(l[id]==r[id]) len[id]=0;
    else len[id]=len[id<<1]+len[id<<1|1];
}

下面来说一下被覆盖至少两次的最大长度怎么求:

首先,如果当前区间整体被覆盖二次,那么显然这个区间被覆盖至少二次的最大长度就是区间本身长度,再者是当前区间是一个点,那么实际表示的长度就是1,那么可以肯定的是这个长度为一的区间被覆盖至少二次的最大长度就是0,这都是比较容易得到的,接下来还有两种情况,这两种情况所表示的区间长度均不为1,一种是当前长度被覆盖次数为1,由于我们在更新线段树时并没有通过父节点更新子节点,那么当前区间子区间所被覆盖的最大长度与当前区间无关,那么当前区间被覆盖至少二次的最大长度就是左右两个子区间被覆盖次数至少一次的区间长度和,加上当前区间被完整覆盖过一次,所以公共部分就至少被覆盖两次,这个地方容易出错,一定要好好理解一下,下面就是最后一种情况,就是当前长度被覆盖次数为0,那这个就没有什么好考虑的,直接就是左右两个区间被覆盖至少两次的区间长度和。

相应代码(此代码为更新至少被覆盖一次的区间最长长度和至少被覆盖两次的区间最长长度的合并代码):

void pushup(int id)
{
    //求取覆盖次数大于等于1的最长区间长度 
    if(cnt[id]) len[id]=alls[r[id]]-alls[l[id]-1];
    else if(l[id]==r[id]) len[id]=0;
    else len[id]=len[id<<1]+len[id<<1|1];
    //求取覆盖次数大于等于2的最长区间长度
    if(cnt[id]>=2)  len2[id]=alls[r[id]]-alls[l[id]-1];
    else if(l[id]==r[id]) len2[id]=0;
    else if(cnt[id]==1) len2[id]=len[id<<1]+len[id<<1|1];//如果当前区间被覆盖一次,则当前区间被覆盖两次的最大长度是左右两个区间被覆盖一次及以上的最大长度和 
    else len2[id]=len2[id<<1]+len2[id<<1|1];
}

还有一个需要特别注意的就是四舍五入,注意精度问题

下面上代码: 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e4+10;
int l[N],r[N],cnt[N];//cnt[i]记录第i个区间被覆盖次数 
double len[N],len2[N];//len[i]记录第i个区间被覆盖次数大于等于1的最大长度,len2[i]记录第i个区间被覆盖次数大于等于2的最大长度
vector<double>alls;
struct node{
	double x,yn,yx;
	int k;
}p[N];
bool cmp(node a,node b)
{
	return a.x<b.x;
}
double find(double x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
void pushup(int id)
{
	//求取覆盖次数大于等于1的最长区间长度 
	if(cnt[id]) len[id]=alls[r[id]]-alls[l[id]-1];
	else if(l[id]==r[id]) len[id]=0;
	else len[id]=len[id<<1]+len[id<<1|1];
	//求取覆盖次数大于等于2的最长区间长度
	if(cnt[id]>=2)  len2[id]=alls[r[id]]-alls[l[id]-1];
	else if(l[id]==r[id]) len2[id]=0;
	else if(cnt[id]==1) len2[id]=len[id<<1]+len[id<<1|1];//如果当前区间被覆盖一次,则当前区间被覆盖两次的最大长度是左右两个区间被覆盖一次及以上的最大长度和 
	else len2[id]=len2[id<<1]+len2[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()
{
	int T,n;
	cin>>T;
	while(T--)
	{
		scanf("%d",&n);
		double x1,y1,x2,y2;
		int cnt=0;
		alls.clear();
		for(int i=1;i<=n;i++)
		{
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			p[++cnt]={x1,y1,y2,1};
			p[++cnt]={x2,y1,y2,-1};
			alls.push_back(y1);
			alls.push_back(y2);
		}
		sort(p+1,p+cnt+1,cmp);
		sort(alls.begin(),alls.end());
		alls.erase(unique(alls.begin(),alls.end()),alls.end());
		for(int i=1;i<=cnt;i++)
		{
			p[i].yn=find(p[i].yn);
			p[i].yx=find(p[i].yx);
		}
		build(1,1,alls.size());
		double ans=0;
		for(int i=1;i<=cnt;i++)
		{
			if(i>1) ans+=len2[1]*(p[i].x-p[i-1].x);
			update_interval(1,(int)p[i].yn,(int)p[i].yx-1,p[i].k);
		}
		printf("%.2lf\n",ans+0.0001);
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值