HDU1255 覆盖的面积(扫描线+离散化+线段树)

题目链接

Problem Description

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

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

思路

这道题的描述与Atlantis比较相似,但是它需要面积被覆盖至少两次。首先的想法是将原先的判断条件改为大于2,却发现普通的扫描线法并没有下传操作,这就会导致在多次覆盖问题时出现问题。于是我们可以思考怎么样考虑到所有的情况,其中一种方法是增加下传操作,将父节点的标记叠加至子节点。此处使用第二种方法,其中较难理解的一步是区间只被覆盖一次时,仍然存在着符合条件的情况:当父节点代表的区间被覆盖一次,则说明它被整体覆盖了一次,此时若其子节点代表的区间同样被覆盖,则同样满足条件,详见代码注释。

代码

#include<map>
#include<stack>
#include<queue>
#include<string>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define ls (k<<1)
#define rs (k<<1|1)
#define pb push_back
#define mid ((l+r)>>1)
using namespace std;
const int mod=1e9+7;
const int maxn=1e5+5;
typedef long long ll;
const int inf=0x3f3f3f3f;
int t,n,cnt,len,tot;
double x1,x2,y1,y2,ans,x[maxn];
struct node
{
	int num;
	double l,r,h;
}a[maxn];
struct edge
{
	int tag;
	double len1,len2;
}b[maxn];
bool cmp(node a,node b)
{
	return a.h<b.h;
}
void uphold(int l,int r,int k)
{
	if(b[k].tag)//当前区间被标记,则把长度加上
		b[k].len1=x[r+1]-x[l];
	else if(l==r)//唯一区间且未被标记(若此区间处于标记状态,则其长度已记录,不会出现区间长度遗漏的情况) 
		b[k].len1=0;
	else
		b[k].len1=b[ls].len1+b[rs].len1;//当父节点未被标记且拥有长度时,将其子节点覆盖的长度和作为新的覆盖长度
	if(b[k].tag>=2)
		b[k].len2=x[r+1]-x[l];//区间被覆盖两次及以上 
	else if(l==r)
		b[k].len2=0;
	else if(b[k].tag==1)
		b[k].len2=b[ls].len1+b[rs].len1;
	else
		b[k].len2=b[ls].len2+b[rs].len2;
}
void update(int l,int r,int l1,int r1,int v,int k)
{
	if(l>=l1&&r<=r1)
	{
		b[k].tag+=v;
		uphold(l,r,k);
		return ;
	}
	if(l1<=mid)
		update(l,mid,l1,r1,v,ls);
	if(r1>mid)
		update(mid+1,r,l1,r1,v,rs);
	uphold(l,r,k);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    scanf("%d",&t);
	while(t--)
	{
		cnt=0;
		scanf("%d",&n);
		memset(b,0,sizeof b);
		for(int i=0;i<n;i++)
		{
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			x[cnt]=x1;
			a[cnt].l=x1;
			a[cnt].r=x2;
			a[cnt].h=y1;
			a[cnt++].num=1;
			x[cnt]=x2;
			a[cnt].l=x1;
			a[cnt].r=x2;
			a[cnt].h=y2;
			a[cnt++].num=-1;
		}
		ans=0;
		sort(x,x+cnt);//用于离散化
		sort(a,a+cnt,cmp);
		len=unique(x,x+cnt)-x;
		for(int i=0;i<cnt;i++)
		{
			ll l=lower_bound(x,x+len,a[i].l)-x;
			ll r=lower_bound(x,x+len,a[i].r)-x-1;//线段树指区间,需要减一使其对应
			update(0,len,l,r,a[i].num,1);
			ans+=b[1].len2*(a[i+1].h-a[i].h);
		}
		printf("%.2f\n",ans);
	}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值