【问题标题】:Finding the maximum area in given binary data查找给定二进制数据中的最大面积
【发布时间】:2010-09-12 21:27:08
【问题描述】:

我在描述用于查找二进制数据的最大矩形区域的算法时遇到问题,其中 1 出现的频率比 0 多 k 倍。数据总是这样的 n^2 位:

例如,n = 4 的数据如下所示:

1 0 1 0
0 0 1 1
0 1 1 1
1 1 0 1

k 的值可以是 1 .. j(k = 1 表示 0 和 1 的数量相等)。

对于上面的数据示例和 k = 1 的解决方案是:

1 0 1 0 0 0 1 1
0 1 1 1
1 1 0 1

但在这个例子中:
1 1 1 0
0 1 0 0
0 0 0 0
0 1 1 1

解决方案是:
1 1 1 0
0 1 0 0
0 0 0 0
0 1 1 1

我尝试了一些蛮力算法,但对于 n > 20,它变得太慢了。你能告诉我应该如何解决这个问题吗?


正如 RBerteig 所提出的 - 问题也可以这样描述:“在一个给定的方形位图中,通过某个任意过程将单元格设置为 1 或 0,找到 1 和 0 以指定比例出现的最大矩形区域, k。”

【问题讨论】:

  • 我不确定我是否理解这个问题。算法应该做什么?
  • 算法应该在给定数据(n x n 位)中找到一个矩形扇区,其中“1”比“0”出现 k 倍。对于 k = 1,算法应该找到这样的数据片段,即“1”与“0”出现的次数相同。如果数据是 1 行,我会使用前缀总和,但在这种情况下,我认为我无法这样做。
  • 这是对问题的正确重述吗? “在一个给定的正方形位图中,单元格被某个任意进程设置为 1 或 0,找到 1 和 0 以指定比例出现的最大矩形区域,k。”
  • 对于上面给出的最后一个示例,为什么由完整的三个右侧列组成的矩形不是“解决方案”?
  • 值得注意(虽然非常明显),要使比率为 k,矩形的面积必须是 (k + 1) 的倍数 - 这将消除很多矩形。

标签: c algorithm optimization binary brute-force


【解决方案1】:

如果实施得当,Bruteforce 在 n

一些观察

  1. 需要考虑 O(n^4) 个子矩形,每个子矩形都可以作为解决方案。
  2. 如果我们能在 O(1)(恒定时间)内找到每个子矩形中 1 和 0 的数量,我们将在 O(n^4) 时间内解决问题。
  3. 如果我们知道某个子矩形中 1 的数量,我们可以找到零的数量(通过区域)。

因此,问题简化为:创建数据结构,允许在恒定时间内找到每个子矩形中 1 的数量

现在,假设我们有子矩形[i0..i1]x[j0..j1]。即,它占据 i0 和 i1 之间的行和 j0 和 j1 之间的列。并让count_ones 成为计算子矩形中1个数的函数。

这是主要观察结果:

count_ones([i0..i1]x[j0..j1]) = count_ones([0..i1]x[0..j1]) - count_ones([0..i0 - 1]x[0..j1]) - count_ones([0..i1]x[0..j0 - 1]) + count_ones([0..i0 - 1]x[0..j0 - 1])

与实际示例相同的观察结果:

AAAABBB
AAAABBB
CCCCDDD
CCCCDDD
CCCCDDD
CCCCDDD

如果我们需要在 D 子矩形 (3x4) 中找到 1 的数量,我们可以通过在整个矩形 (A + B + C + D) 中取 1 的数量,减去 (A) 中的 1 的数量来完成+ B) 矩形,在 (A + C) 矩形中减去 1 的数量,并在 (A) 矩形中添加 1 的数量。 (A + B + C + D) - (A + B) - (A + C) + (A) = D

因此,我们需要表格sums,对于每个ij,在子矩形[0..i][0..j] 中包含1 的个数。
您可以在 O(n^2) 中创建此表,但即使是直接填充它的方法(对于每个 ij 迭代 [0..i][0..j] 区域的所有元素)也将是 O(n^4)。

有了这张桌子,

count_ones([i0..i1]x[j0..j1]) = sums[i1][j1] - sums[i0 - 1][j1] - sums[i1][j0 - 1] + sums[i0 - 1][j0 - 1]

因此,时间复杂度达到了 O(n^4)。

【讨论】:

  • 恐怕 n
  • @alg0 那么你需要什么复杂度呢?
【解决方案2】:

这仍然是蛮力,但您应该注意的是,您不必为新的i*j 矩形重新计算所有内容。相反,对于每个可能的矩形大小,您可以一次将矩形移过n*n 网格,减少不再位于矩形内的位的计数,并增加新进入矩形的位的计数。您可以将其与改变矩形大小结合起来,并尝试找到移动和调整矩形大小的最佳模式。

【讨论】:

  • 我正在考虑那个解决方案,但是当我试图估计步骤数时,我发现它太复杂了。对于 n = 200(40,000 位),应检查 30,000 个不同大小的“窗口”: len([(x, y) for x in range(1, 201) for y in range(1, 201) if not ( x * y) % 2]) -- 使用 Python 来计算。但是 - 每个窗口显示都移动到 n*n 中。这意味着应该移动 2x1 矩形 (200 - 1) * (200 - 2) = 39402 次,3x1 - 39203 ... 等等。对于 n = 200,将有 ~300,000,000 次移动。
  • 您还可以缓存访问过的每个子矩形的总数,并将它们用于构建较大子矩形的聚合总数。
  • @RBerteig:对于数据:0 0 0 0 | 0 0 0 0 | 1 1 1 1 | 1 1 1 1 |我怎么能使用提到的缓存?
  • 一旦你对左上角的 2x2 求和并得到 0,在计算顶部 2x4 的总和时,请记住这个事实和相邻 2x2 的总和。我认为由此产生的分辨率金字塔会牺牲内存来多次重新访问同一个像素。
【解决方案3】:

只是一些提示..

您可以对这些值施加更好的限制。需求导致条件

N1*(k+1) == S*k,其中N1 是一个区域内的个数,S=dx*dy 是它的表面。 可以改写成更好的形式:

N1/k == S/(k+1)

因为数字nn+1 的最大公约数始终为1,所以N1 必须是kdx*dy 的倍数才能成为k+1 的倍数。它大大减少了解决方案的可能空间,k 越大越好(对于dx*dy 情况,您需要使用k+1 的素数除数)。

现在,由于您只需要具有此类属性的最大区域的表面,因此明智的做法是从最大区域开始,然后移至较小的区域。通过尝试从 n^2 到满足除数和边界条件的 dx*dyk+1,您会发现解决方案非常快,比 O(n^4) 快得多,因为一个特殊的原因:例外情况当专门构造数组时,如果我们假设一个随机输入,那么在具有表面S(n-dx+1)*(n-dy+1) 区域中,S 值中有N1 的概率将随着@987654342 的减少而不断增长@。 (k 的较大值会使概率变小,但同时它们会使 dxdy 对的过滤器更强)。

另外,这个问题:http://ioinformatics.org/locations/ioi99/contest/land/land.shtml,看起来有点相似,也许你会在他们的解决方案中找到一些想法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-01
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 2019-07-24
    • 1970-01-01
    相关资源
    最近更新 更多