HDU 1542 Atlantis(扫描线经典入门题

Problem Description

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

题意

求矩形覆盖的面积总和

分析

首先介绍扫描线的思想


先给个图
下面开始介绍扫描线!


看这个图,我们将矩形每一个宽作为一个边界。
总面积可以看作是三个部分的矩形面积加起来!
我们定义当前的有效长度为当前矩形的宽
红色的线就是我们说的扫描线,用这条线从左往右扫,每次扫到一个边界,就更新当前的有效长度。
那么 = 每一部分的面积= 有效长度 * 边界的距离 =
这就是扫描线的思想。

接下来介绍具体实现

首先为了从左往右扫描,当然要将每一条宽按 x x x 从小到大排序。
每一条宽储存的是 y 1 y_1 y1 y 2 y_2 y2
用扫描线扫描之后,我们将问题转化成这样一个小问题:
有若干条线段,怎么维护并起来的总长度
问题来了,题目中给的坐标是 d o u b l e double double 类型的。无法直接用线段树啊!
所以要离散化!
我们将每个 y y y 从大到小进行排名。
如图

这样子我们对于每一条线段,都能得知他覆盖的区间。
比如黑色线段覆盖了 [ 1 , 4 ] [1,4] [1,4],蓝色覆盖了 [ 2 , 6 ] [2,6] [2,6],红色覆盖了 [ 3 , 5 ] [3,5] [3,5]
但是点是离散的,线段是连续的,所以我们用覆盖了那些线段来表示。
这里首先定义 i i i i + 1 i+1 i+1 这条线段的编号是 i i i(用点的编号来指代连着的边
假设有 m m m 个点,那么有 m 1 m-1 m1 条边,编号为 1 1 1 m 1 m-1 m1
所以黑色线段覆盖了 [ 1 , 3 ] [1,3] [1,3] 的线段,蓝色覆盖了 [ 2 , 5 ] [2,5] [2,5] 的线段,红色覆盖了 [ 3 , 5 ] [3,5] [3,5]的线段
于是我们可以用线段树来进行维护了。

线段树里该存什么?

首先要存的肯定是有效长度了。
还要存的是一个标记,指的是当前节点代表的区间被覆盖了多少层(注意这是区间的性质,不由子区间来,也不由父区间来,即这个标记既不 p u s h u p pushup pushup 也不 p u s h d o w n pushdown pushdown)。
所以要存的是一个标记还有有效长度。
然后就可以进行更新了。
更新部分代码如下:

void update(int l, int r, int rt, int a, int b, int c){
	if(l >= a && r <= b){
		tag[rt] += c;
		pushup(rt, l, r);
		return;
	}
	int m = l + r >> 1;
	if(a <= m) update(lson, a, b, c);
	if(b > m) update(rson, a, b,c);
	pushup(rt, l, r);
}

可以说是很朴素了
确实很朴素啊!
p u s h u p pushup pushup 代码如下:

void pushup(int rt, int l, int r){
	if(add[rt]) sum[rt] = e[r + 1] - e[l];
	else sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

也是很简单的啦
非常非常好理解

总代码如下

#include <bits/stdc++.h>
#define N 100005
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
struct node{
	double l, r, x;
	int f;
	bool operator < (const node & A) const{
		return x < A.x;
	}
}line[N * 2];
int add[N * 4], ret, tot, cnt;
double sum[N * 4], a, b, c, d, ans, e[N * 2];
set<double> s;
void cr(double a, double b, double c, int d){
	line[++tot].x = a; line[tot].l = b; line[tot].r = c; line[tot].f = d;
}
void pushup(int rt, int l, int r){
	if(add[rt]) sum[rt] = e[r + 1] - e[l];
	else sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void update(int l, int r, int rt, int a, int b, int c){
	if(l >= a && r <= b){
		add[rt] += c;
		pushup(rt, l, r);
		return;
	}
	int m = l + r >> 1;
	if(a <= m) update(lson, a, b, c);
	if(b > m) update(rson, a, b, c);
	pushup(rt, l, r);
}
int main(){
	int i, j, n, m, l, r;
	while(scanf("%d", &n)){
		if(!n) break;
		ret++;
		tot = ans = cnt = 0;
		memset(sum, 0, sizeof(sum));
		memset(add, 0, sizeof(add));
		for(i = 1; i <= n; i++){
			scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
			cr(a, b, d, 1);
			cr(c, b, d, -1);
			e[++cnt] = b; e[++cnt] = d;
		}
		sort(line + 1, line + tot + 1);
		sort(e + 1, e + cnt + 1);
		m = unique(e + 1, e + cnt + 1) - e - 1;
		for(i = 1; i < tot; i++){
			l = lower_bound(e + 1, e + cnt + 1, line[i].l) - e;
			r = lower_bound(e + 1, e + cnt + 1, line[i].r) - e;
			update(1, m, 1, l, r - 1, line[i].f);	
			ans += sum[1] * (line[i + 1].x - line[i].x);
		}
		printf("Test case #%d\n", ret);
		printf("Total explored area: %.2lf\n", ans);
		printf("\n");
	}
	return 0;
} 
各种题解及学习笔记~ 文章被收录于专栏

各种题解及学习笔记

全部评论

相关推荐

个人背景:学院二本计科专业&nbsp;大二开始实习个人经历:安克创新&nbsp;、理想汽车、字节跳动碎碎念:我做事只有三分钟热度。看到进了大厂的同学,我会羡慕,也会跟着努力上进;但遇到好看的小说,我又会放下手头的事沉迷其中,之前的坚持也就中断了。我有些自卑,总觉得自己学历和外貌都不够好。之前偶然在网上受到关注,我就喜欢上了上网,因为这里有很多人认可我。但我也很在意别人的评价,偶尔看到嘲讽的言论,会触发我的自卑情绪,让我感到愤怒。有时候我会强硬地回怼,有时候又会懦弱地选择无视。我也有虚荣心。不管是拿到安克、理想还是字节的机会,我在分享的时候都会带着这份心思。我会特意强调自己学历不好,是为了衬托出过程的艰难,以此显得自己更厉害。我知道,人往往会炫耀自己缺少的东西,来掩盖内心的空洞。我总想着走捷径,不太喜欢踏踏实实地做事。找实习的时候,我花了更多时间在研究面试技巧上,而不是提升专业能力。我会反复听面试录音分析技巧,看面试教程学习怎么和不同的面试官沟通,还会每天自言自语练习语言表达,同学都觉得我有点奇怪。我的实习生涯里,侥幸和运气占了很大一部分。我总在想,如果有一天我失去了这份幸运,这些特质可能会让我一蹶不振。ps:&nbsp;很多人会问我学习路线和经验&nbsp;但是就像我上面说的&nbsp;我的实习过程靠的很多是关键节点的运气&nbsp;技术上面我可能不如很多人&nbsp;&nbsp;所以请大家理性求助和理性参考我的回答&nbsp;附上我的投递记录
我的offer在哪里...:从去年看到现在,飞升哥就是榜样
我的求职进度条
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务