数算实习作业 Picture(扫描线)

题目描述

A number of rectangular posters, photographs and other pictures of the same shape are pasted on a wall. Their sides are all vertical or horizontal. Each rectangle can be partially or totally covered by the others. The length of the boundary of the union of all rectangles is called the perimeter.

Write a program to calculate the perimeter. An example with 7 rectangles is shown in Figure 1

The corresponding boundary is the whole set of line segments drawn in Figure 2.

The vertices of all rectangles have integer coordinates.

输入

Your program is to read from standard input. The first line contains the number of rectangles pasted on the wall. In each of the subsequent lines, one can find the integer coordinates of the lower left vertex and the upper right vertex of each rectangle. The values of those coordinates are given as ordered pairs consisting of an x-coordinate followed by a y-coordinate.

0 <= number of rectangles < 5000
All coordinates are in the range [-10000,10000] and any existing rectangle has a positive area.

输出

Your program is to write to standard output. The output must contain a single line with a non-negative integer which corresponds to the perimeter for the input rectangles.

样例输入

7
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4
2 15 10 22
30 10 36 20
34 0 40 16

样例输出

228

题解

ATP还是第一次写扫描线求周长的题(大概叭?)。。。当然要发篇题解纪念一下辣(x

这个做法是基于扫描线求面积的那种线段树的。它可以维护全局被覆盖的总块数,但是因为没有pushdown,不能求具体某个区间被覆盖的块数。

关键在于维护两个数组sum和cov。cov是记录一个区间被完全覆盖的次数,而sum是维护区间内被覆盖过的总块数。因为没有pushdown,只有sum[1]也就是根节点是保证正确的。

update的时候,如果这个区间被完全覆盖,那么sum就是区间长度。否则这个区间没有被完全覆盖,如果这个区间长度还只有1,那么它一定是没有被覆盖;如果区间长度大于1,就二分用两个子节点的sum来累加。

这种线段树具体参考zyf2000的这篇题解

有了这种线段树以后,就可以考虑求周长的问题了。这里将水平和垂直的线段分别处理。以垂直线段为例,从左往右扫描,每个矩形左边的线段赋值为1,右边的线段赋值为-1。

观察可以发现,每一段对周长的贡献是一个增量。

设当前被覆盖的总长度为A,加入一条线段后被覆盖的总长度为B,那么显然周长增加了B-A。

设当前被覆盖的总长度为A,去掉一条线段后被覆盖的总长度变成了B,那么显然周长增加了A-B(这个例子可以参考样例中间被挖空的部分)

需要注意的就是如果有若干位置相同的线段,应该先处理加入的线段,再处理去掉的线段。否则会造成重复累加。举例的话可以比如说两个并排放置,一条边重合的矩形。

最后一个细节就是,因为这道题中线段是以端点的形式给出的,而线段树的每个节点实际上代表的是一个单位区间,相当于是给单位区间编了号,所以记录线段的时候右端点要减一。举例来说,[0, 10]这个区间覆盖的是编号为[0, 10)即[0,9]的单位区间。
注意这道题坐标可能是负数,所以加了一个偏移量。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define BASE 10001
#define MAX 20010
using namespace std;
int n, Max, ans, sum[100010], cov[100010], dlt[100010];
struct segment {
	int l, r, pos, val;
	segment(){l = r = pos = val = 0;}
	segment(int _l, int _r, int _pos, int _val){l = _l; r = _r; pos = _pos; val = _val;}
	bool operator < (const segment &a) const {
		return pos < a.pos || (pos == a.pos && val > a.val);
	}
}h[20010], v[20010];
void update(int i, int l, int r) {
	if (cov[i] > 0) sum[i] = r - l + 1;
	else if (l == r) sum[i] = 0;
	else sum[i] = sum[i << 1] + sum[(i << 1) + 1];
}
void add(int i, int l, int r, int left, int right, int val) {
	if (left <= l && right >= r) {
		cov[i] += val;
		update(i, l, r);
		return;
	}
	int mid = (l + r) >> 1;
	if (left <= mid) add(i << 1, l, mid, left, right, val);
	if (right > mid) add((i << 1) + 1, mid + 1, r, left, right, val);
	update(i, l, r); 
}
int Calc(segment* s) {
	int ans = 0, oldval;
	sort(s + 1, s + 2 * n + 1);
	memset(sum, 0, sizeof(sum));
	memset(cov, 0, sizeof(cov));
	memset(dlt, 0, sizeof(dlt));
	for (int i = 1, last; i <= 2 * n;) {
		last = s[i].pos;
		while (s[i].pos == last && s[i].val == 1) {
			oldval = sum[1];
			add(1, 1, MAX, s[i].l, s[i].r, 1);
			ans += sum[1] - oldval;
			i += 1;
		}
		while (s[i].pos == last && s[i].val == -1) {
			oldval = sum[1];
			add(1, 1, MAX, s[i].l, s[i].r, -1);
			ans += oldval - sum[1];
			i += 1;
		}
	}
	return ans;
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {
		int x, y, xx, yy;
		scanf("%d%d%d%d", &x, &y, &xx, &yy);
		x += BASE; y += BASE;
		xx += BASE; yy += BASE;
		h[(i << 1) - 1] = segment(x, xx - 1, y, 1);
		h[i << 1] = segment(x, xx - 1, yy, -1);
		v[(i << 1) - 1] = segment(y, yy - 1, x, 1);
		v[i << 1] = segment(y, yy - 1, xx, -1);
	}
	ans = Calc(v) + Calc(h);
	printf("%d
", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/FromATP/p/13871626.html