覆盖的面积 HDU - 1255 2种思路透彻理解扫描线

一、内容

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

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

二、思路

  • 覆盖一次的扫描线模板
  • 第一种思路:我们只需要每次求出包含2次以上的长度即可,由于我们扫描线在update()和查询的时候都是没有进行pushdown()操作的,在求包含一次以上的长度时不会出现错误,但是当2次以上的时候有些区间便会出错(因为父亲的标记没有下方到这个子区间)。故我们只需改变原来这个优化,在查询和修改的时候恢复它的标记下放。在pushup的时候修改下判断,判断2次以上的长度。最后查询的len就是最终包含2次以上的长度了。
  • 第二种思路:还是按照优化的策略, 只是对线段树新增了一个属性len2(代表包含2次以上的长度)。
    1.当cnt >= 2的时候,代表的收这个区间是被完整覆盖了2次以上,那么len2直接等于这个区间的长度即可。
    2.当cnt==1的时候,代表这个区间被完整覆盖了1次,那么若子区间是被覆盖了一次,那么必定加上父区间的一次,肯定是2次以上。 所以len2 = 左区间的len1 + 右区间的len1. (len1是包含了1次以上的长度)
    3.当cnt == 0的时候,代表这个区间没有被完全包含,那么这时候的len2可以从2个子区间的len2得来。
    4.根节点,若根节点的cnt<=1,那么直接等于0,因为它无子区间。
  • 最后说的是题目样例有误差。好像是四舍五入了。

三、代码

第一种思路:

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2005;
struct Line {
	int st; //代表1 -1 2种状态 
	double s, e, x;
	bool operator < (const Line & w) const {
		return x < w.x; //按照x小的排在前面 
	}
} line[N];
struct Node {
	int cnt, lazy; //代表被包含的次数 
	double len; //这个区间被包含的长度 
} tr[N << 2]; 
int n, cnt, t; // cnt代表离散化后数组里面元素的个数 
double fy[N], x1, x2, y1, y2; 
void build(int id, int l, int r) {
	tr[id].cnt = tr[id].lazy = tr[id].len = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(id << 1, l, mid);
	build(id << 1 | 1, mid + 1, r);
}
void pushup(int id, int l, int r) {
	if (tr[id].cnt >= 2) { // 代表id这个区间被覆盖了  
		tr[id].len = fy[r + 1] - fy[l]; //因为一个点代表的是一个区间 那r这个点的区间 就是[fy[r], fy[r + 1]]这段长度 
	} else if (l != r) {  //如果这整个区间没有被包含 那么就由儿子区间组成 
		tr[id].len = tr[id << 1].len + tr[id << 1 | 1].len; 
	} else tr[id].len = 0; //叶子结点  
}
void pushdown(int id, int l, int r) {
	if (tr[id].lazy == 0) return;
	tr[id << 1].cnt += tr[id].lazy;
	tr[id << 1 | 1].cnt += tr[id].lazy;
	tr[id << 1].lazy += tr[id].lazy;
	tr[id << 1 | 1].lazy += tr[id].lazy;
	tr[id].lazy = 0;
}
void query(int id, int l, int r) {
	if (l == r) {
		pushup(id, l, r);
		return; 
	}
	pushdown(id, l, r); //查询的时候将所有层次的标记进行下放  
	int mid = (l + r) >> 1;
	query(id << 1, l, mid);
	query(id << 1 | 1, mid + 1, r);
	pushup(id, l, r);
}
void update(int id, int l, int r, int x, int y, int d) {
	if (x <= l && r <= y) {
		tr[id].cnt += d;
		tr[id].lazy += d;
		pushup(id, l, r); //及时更新这段区间 因为父亲区间可能需要用到我这个区间的len 
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(id, l, r); 
	if (x <= mid) update(id << 1, l, mid, x, y, d);
	if (y > mid) update(id << 1 | 1, mid + 1, r, x, y, d);
	pushup(id, l, r); 
} 
int find(double y) {
	return lower_bound(fy + 1, fy + 1 + cnt, y) - fy; 
}
int main() {
	scanf("%d", &t); 
	while (t--) {
		scanf("%d", &n);
		for (int i = 1, j = 1; i <= n; i++) {
			scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
			line[j].s = y1, line[j].e = y2, line[j].x = x1, line[j].st = 1, fy[j++] = y1;
			line[j].s = y1, line[j].e = y2, line[j].x = x2, line[j].st = -1, fy[j++] = y2;
		}
		sort(line + 1, line + 1 + 2 * n);
		sort(fy + 1, fy + 1 + 2 * n);
		cnt = unique(fy + 1, fy + 1 + 2 * n) - fy - 1; //获取去重后元素的个数
		build(1, 1, cnt - 1); //一个点代表的是一个区间, 一共有cnt个点 所以只有cnt-1个区间
		double ans = 0;
		for (int i = 1; i <= 2 * n; i++) {
			query(1, 1, cnt - 1); //先查询一次才能够使用1这个节点的区间 
			ans += tr[1].len * (line[i].x - line[i - 1].x);
			update(1, 1, cnt - 1, find(line[i].s), find(line[i].e) - 1, line[i].st); //这里-1是因为每个点代表的是一个区间 我们不能包含e那个点所代表的区间 
		} 
		printf("%.2lf\n", ans); 
	}
	return 0;
}

第二种思路(较快):

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2005;
struct Line {
	int st; //代表1 -1 2种状态 
	double s, e, x;
	bool operator < (const Line & w) const {
		return x < w.x; //按照x小的排在前面 
	}
} line[N];
struct Node {
	int cnt; //代表被包含的次数 
	double len1, len2; //len1代表包含1次的长度 len2代表包含2次以上的长度 
} tr[N << 2]; 
int n, cnt, t; // cnt代表离散化后数组里面元素的个数 
double fy[N], x1, x2, y1, y2; 
void build(int id, int l, int r) {
	tr[id].cnt = tr[id].len1 = tr[id].len2 = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(id << 1, l, mid);
	build(id << 1 | 1, mid + 1, r);
}
void pushup(int id, int l, int r) {
	if (tr[id].cnt){ // 代表id这个区间被覆盖了  
		tr[id].len1 = fy[r + 1] - fy[l]; //因为一个点代表的是一个区间 那r这个点的区间 就是[fy[r], fy[r + 1]]这段长度 
	} else if (l != r){ //如果这整个区间没有被包含 那么就由儿子区间组成 
		tr[id].len1 = tr[id << 1].len1 + tr[id << 1 | 1].len1; 
	} else tr[id].len1 = 0; //叶子结点  
	//当cnt >= 2的时候
	if (tr[id].cnt >= 2) {
		tr[id].len2 = fy[r + 1] - fy[l];
	} else if (l != r && tr[id].cnt == 1)  { //当cnt == 1的时候 子区间若被包含了一次那么加上父亲的一次就是2次了		
		tr[id].len2 = tr[id << 1].len1 + tr[id << 1 | 1].len1; 
	} else if (l != r){ //这种情况若不是根节点 那么便可以通过子节点的len2得来 
		tr[id].len2 = tr[id << 1].len2 + tr[id << 1 |  1].len2;   
	} else { ////根节点 或者 不满足上面条件的都直接等于0 
		tr[id].len2 = 0;
	} 
}
void update(int id, int l, int r, int x, int y, int d) {
	if (x <= l && r <= y) {
		tr[id].cnt += d;
		pushup(id, l, r); //及时更新这段区间 因为父亲区间可能需要用到我这个区间的len 
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) update(id << 1, l, mid, x, y, d);
	if (y > mid) update(id << 1 | 1, mid + 1, r, x, y, d);
	pushup(id, l, r); 
} 
int  find(double y) {
	return lower_bound(fy + 1, fy + 1 + cnt, y) - fy; 
}
int main() {
	scanf("%d", &t);
	while (t--) {
		scanf("%d", &n);
		for (int i = 1, j = 1; i <= n; i++) {
			scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
			line[j].s = y1, line[j].e = y2, line[j].x = x1, line[j].st = 1, fy[j++] = y1;
			line[j].s = y1, line[j].e = y2, line[j].x = x2, line[j].st = -1, fy[j++] = y2;
		}
		sort(line + 1, line + 1 + 2 * n);
		sort(fy + 1, fy + 1 + 2 * n);
		cnt = unique(fy + 1, fy + 1 + 2 * n) - fy - 1; //获取去重后元素的个数
		build(1, 1, cnt - 1); //一个点代表的是一个区间, 一共有cnt个点 所以只有cnt-1个区间
		double ans = 0;
		for (int i = 1; i <= 2 * n; i++) {
			ans += tr[1].len2 * (line[i].x - line[i - 1].x);
			update(1, 1, cnt - 1, find(line[i].s), find(line[i].e) - 1, line[i].st); //这里-1是因为每个点代表的是一个区间 我们不能包含e那个点所代表的区间 
		} 
		printf("%.2lf\n", ans); 
	}
	return 0;
}
发布了355 篇原创文章 · 获赞 285 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/104092527