Atlantis HDU - 1542(线段树+扫描线入门)

Atlantis

HDU - 1542

There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity.

Input

The input file consists of several test cases. Each test case starts with a line containing a single integer n (1<=n<=100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0<=x1<x2<=100000;0<=y1<y2<=100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area.

The input file is terminated by a line containing a single 0. Don’t process it.

Output

For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point.

Output a blank line after each test case.

Sample Input

2
10 10 20 20
15 15 25 25.5
0

Sample Output

Test case #1
Total explored area: 180.00 

问题:给出若干个矩形,(给的是矩形左上角和右下角坐标),求最后所得图形的面积/周长;

三个矩形如左图所示,而若要计算面积,看右图,用3个矩形各自的面积之和减去重复部分(红色和蓝色)的面积

人算很简单,但是用算法怎么实现呢?

此类问题一般都是用线段树辅助扫描法来计算;

什么是扫描法?有什么用?怎么用?

可以想象成一根假想的线,将图从左往右或从右往左或自下而上或自上而下“扫描”一遍,至于扫描的是什么则根据具体应用选择。

扫描线可以计算矩形面积、周长,可以计算线段交点,可以实现多边形扫描转换,在图的处理方面经常用到。

这里总结一下扫描线计算矩形面积和周长的算法。

怎么用?首先,对于之前的图,除了用总面积减去重合面积,还可以换一种计算方法,如图:

此图用4条横线将整个图划分成了5个部分,显然此时再算面积就可以用各个颜色的部分求和。

想想,这样计算的整个慢过程:

假设我们的视线自下而上,首先,我们看到了最下面灰色矩形的下边,

用这个下边的长度乘以这条边和上一条边的高度差即得到灰色矩形面积,

继续看到蓝色的矩形的下边,虽然蓝色矩形有两个,但我们计算时自然会用结合律将两个矩形的下边加起来再去乘以同样的高,

然后重复这样的操作,我们最终可以求得整个图形的面积。

但是,这依旧是人做的,计算机要怎么实现呢?

首先的问题是,计算机要怎么保存这张图这些矩形?

从刚才的过程,我们不难发现,我们只需要保存这张图里面的所有水平的边即可。

对于每条边,它所拥有的属性是:这条边的左右端点(的横坐标),这条边的高度(纵坐标),这条边属于矩形的上边还是下边(想想为什么保存这个属性)

刚刚计算中我们遇到两个蓝色矩形的一部分一眼就能看出这两个蓝色矩形的‘宽’是多少,用计算机怎么做到?

线段树华丽登场!

我们以整个图最左边的竖线作为区间左端点,最右边的竖线作为区间右端点,去维护这个区间的有效长度(即被覆盖的长度

比如扫到第2条边的时候,有效长度就是两个蓝色矩形的宽之和。

这样,我们用扫描线去扫描每一条边的时候,都需要更新线段树的有效长度

是如何更新的呢?

如果扫到的这条边是某矩形的下边,则往区间插入这条线段

如果扫到的这条边是某矩形的上边,则往区间删除这条线段

为什么?自己试着模拟一下就不难发现:

因为我们是自下而上的扫这个图,扫到下边相当于刚刚进入一个矩形,扫到上边则是要离开一个矩形

利用线段树把每条边的有效长度找到了,也就是找到了每部分的所有矩形的总宽,那么高呢?

高就简单多了,对于所有的边,按照高度从小到大排列,那么矩形高就是每相邻边之间的高度差

这题横坐标略大,需要离散化处理

#include <bits/stdc++.h>
using namespace std;
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l+q[i].r)>>1)
typedef long long ll;
const int N = 111;
struct Edge{//存边的结构体
	double l,r;//左右端点x坐标
	double h;//高度
	int f;//标记上下边,下边-1,上边1
	bool operator < (const Edge &a)const{//按照高度从小到大排序
		return h < a.h;
	}
}e[N<<1];
struct Node{//线段树
	int l,r;//左右区间
	int s;//边是否完全覆盖这个区间
	double len;//区间边长度
}q[N*8];
double x[2*N];
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){//如果左右端点相等长度为0
		q[i].len = 0;
	}
	else{//如果不是完全覆盖,边长度是它孩子长度和
		q[i].len = q[ls].len + q[rs].len;
	}
}
void update(int i,int l,int r,int xx){
	if(q[i].l == l && q[i].r == r){//找到这个区间,因为这长度为这个区间到边出现所以检查上下边
		q[i].s += xx;//如果是下边-1,初始为0加上xx后变成-1,说明加入这条边,pushup更新时就是区间长度
		             //如果是上边1,此时加上后变回0,长度就会变成这个区间到左右儿子到长度和
		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;
	int cas = 0;
	while(scanf("%d",&n) != EOF && 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[tot+1];
			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);
		sort(x,x+tot);//因为x值很大所以需要坐标离散化
		int k = 1;
		for(int i = 1; i < tot; i++){
			if(x[i] != x[i-1]){//去重,默认x[0]已经放好,从1开始
				x[k++] = x[i];
			}
		}//得到区间[0,k-1]
		build(1,0,k-1);//线段树实际区间其实是x点到位置的区间
		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;
			//区间[l,r)
			update(1,l,r,e[i].f);//不断更新跟节点总长度
			ans += (e[i+1].h - e[i].h) * q[1].len;//相邻高度差乘宽度
		}
		printf("Test case #%d\n",++cas);
		printf("Total explored area: %.2f\n\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/codeswarrior/article/details/81079154
今日推荐