我想知道解决这个问题的有效方法:
给定左上角和右下角的N个矩形,请找到N个矩形的并集周长。
我只有O(N^2)
算法而且速度太慢,所以请找到更有效的算法。
您可以假设坐标值是正整数且小于100000。
O(n ^ 2)算法:
for x=0 to maxx
for i=0 to N
if lowx[i] = x
for j=lowy[i] to highy[i]
d[j]++
if d[j] = 1 then ret++
if highy[i] = x
for j=lowy[i] to highy[i]
d[j]--
if d[j] = 0 then ret++
for y=0 to maxy
if d[y] = 0 && d[y + 1] >= 1 then ret++
if d[y] >= 1 && d[y + 1] = 0 then ret++
最后的回答是答案。
答案 0 :(得分:1)
一方面,这个问题的经典解决方案是基于扫描线的“布尔合并”算法,该算法以其原始形式构建这些矩形的并集,即构建结果的多边形边界。可以轻松修改算法以计算结果边界的周长,而无需在物理上构建它。
另一方面,基于扫描线的“布尔合并”可以为任意多边形输入执行此操作。鉴于在您的情况下输入受到更多限制(和简化) - 只是一堆同构矩形 - 很可能存在更轻量级和更聪明的解决方案。
注意,顺便说一下,这种矩形的联合实际上可能是一个多连通的多边形,即一个有洞的区域。
答案 1 :(得分:1)
在某些情况下,这可能会有所帮助:
考虑一下,
_______
| |_
| |
| _|
|___ |
| |
|___|
的周长与此相同:
_________
| |
| |
| |
| |
| |
|_________|
答案 2 :(得分:1)
有一个O(n log n)时间扫描线算法。应用以下步骤来计算形状的垂直周长。转置输入并再次应用它们来计算水平周长。
对于每个矩形,准备一个由左侧x坐标键入的起始事件,其值为y-间隔,以及由右侧x坐标键入的停止事件,其值为y-间隔。按x坐标对这些事件进行排序,并按顺序处理它们。在任何时候,我们都保持一个数据结构,能够报告边界与扫描线相交的点数。在事件点之间的2n - 1个间隔,我们将该数字乘以间隔宽度到周长。
我们需要的数据结构在时间O(log n)中支持以下操作。
insert(ymin, ymax) -- inserts the interval [ymin, ymax] into the data structure
delete(ymin, ymax) -- deletes the interval [ymin, ymax] from the data structure
perimeter() -- returns the perimeter of the 1D union of the contained intervals
由于输入坐标是有界整数,因此可以通过segment tree实现一种可能的实现。 (实际输入有一个扩展,包括对输入的y坐标进行排序并将它们重新映射为小整数。)每个段都有一些相关的数据
struct {
int covers_segment;
bool covers_lower;
int interior_perimeter;
bool covers_upper;
};
其范围是输入间隔中存在的从其下降的段的并集。 (请注意,非常长的段对树的最底层没有影响。)
covers_segment
的含义是它是分解中具有该段的区间数。 covers_lower
的含义是,如果其中一个具有相同下端点的段之一属于某个区间的分解,那么它就是真的。 interior_perimeter
的含义是范围内段的1D周长(如上所述)。 covers_upper
的含义类似于covers_lower
,具有上端点。
这是一个例子。
0 1 2 3 4 5 6 7 8 9
[---A---]
[---B---] [-D-]
[-C-]
间隔时间为A ([0, 4])
和B ([2, 4], [4, 6])
以及C [3, 4] [4, 5]
和D [7, 8] [8, 9]
。
c_s c_l i_p c_u
[0, 1] 0 F 0 F
[0, 2] 0 F 0 F
[1, 2] 0 F 0 F
[0, 4] 1 T 0 T
[2, 3] 0 F 0 F
[2, 4] 1 T 1 T
[3, 4] 1 T 0 T
[0, 8] 0 T 2 F
[4, 5] 1 T 0 T
[4, 6] 1 T 1 T
[5, 6] 0 F 0 F
[4, 8] 0 T 2 F
[6, 7] 0 F 0 F
[6, 8] 0 F 1 F
[7, 8] 1 T 0 T
[0, 9] 0 T 2 T
[8, 9] 1 T 0 T
要插入(删除)间隔,请通过递增(递减)covers_segment
来插入(删除)其组成段。然后,对于受影响的段的所有祖先,重新计算其他字段,如下所示。
if s.covers_segment == 0:
s.covers_lower = s.lower_child.covers_lower
s.interior_perimeter =
s.lower_child.interior_perimeter +
(1 if s.lower_child.covers_upper != s.upper_child.covers_lower else 0) +
s.upper_child.interior_perimeter
s.covers_upper = s.upper_child.covers_upper
else:
s.covers_lower = true
s.interior_perimeter = 0
s.covers_upper = true
要实施perimeter
,请返回
(1 if root.covers_lower else 0) +
root.interior_perimeter +
(1 if root.covers_upper else 0)
其中root
是段树的根。