这是来自一个古老的奥林匹克练习题:

假设您有一个 1000x1000 的网格,其中单元格 (i,j) 包含数字 i*j。 (行和列从 1 开始编号。)

在每一步,我们从旧网格构建一个新网格,其中每个单元格 (i,j) 包含最后一个网格中 (i,j) 的“邻域平均值”。 “邻域平均值”定义为像元及其最多 8 个邻域的平均值的下限。例如,如果网格角落的 4 个数字是 1、2、5、7,在下一步中,角落将计算为 (1+2+5+7)/4 = 3。

最终我们将达到所有数字都相同并且网格不再改变的点。目标是弄清楚达到这一点需要多少步骤。

我尝试简单地模拟它,但这不起作用,因为答案似乎是 O(n^2) 步,每个模拟步骤都需要 O(n^2) 来处理,结果是 O(n^4)对于 n=1000 来说太慢了。

有没有更快的方法来做到这一点?

最佳答案

“地板”步骤让我怀疑不太可能有分析解决方案,这实际上是一个微观优化练习。这是我的想法。

让我们暂时忽略角落和边缘。他们只有3996个,无论如何他们都需要特殊对待。

对于内部单元格,您需要添加 9 个元素以获得其下一个状态。但是反过来说:每个内部单元格都必须是 8 个附加单元的一部分。

或者是吗?从三个连续的行 A[i]B[i]C[i] 开始,并计算三个新行:

A'[i] = A[i-1] + A[i] + A[i+1]
B'[i] = B[i-1] + B[i] + B[i+1]
C'[i] = C[i-1] + C[i] + C[i+1]

(请注意,由于 A'[i+1] = A'[i] - A[i-1] + A[i+1] ,您可以使用“滑动窗口”稍微更快地计算每一个。相同数量的算术运算但更少的负载。)

现在,要在 B[j] 位置获取新值,您只需计算 A'[j] + B'[j] + C'[j]

到目前为止,我们还没有保存任何工作;我们刚刚对添加的内容进行了重新排序。

但是现在,计算更新后的行 B ,您可以丢弃 A' 并计算下一行:
D'[i] = D[i-1] + D[i] + D[i+1]

...您可以使用数组 B'C' 来计算行 C 的新值,而无需重新计算 B'C' 。 (当然,您可以通过将行 B'C' 移动为 A'B' 来实现这一点……但这种方式更容易解释。也许吧。我想。)

对于每一行,比如说 B ,我们扫描它一次以产生 B'2n 算术运算,第二次计算更新的 B 也需要 2n 运算,所以我们总共对每个元素进行四次加法/减法,而不是八次。

当然,在实践中,您会在更新 C' 的同时计算 B 以实现相同数量的操作但更好的局部性。

这是我唯一的结构性想法。 SIMD 优化专家可能有其他建议...

关于algorithm - 模拟矩阵运算的快速方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24101774/

10-13 08:59