【问题标题】:Counting the total number of even/odd numbers in a 2D graph计算二维图中偶数/奇数的总数
【发布时间】:2022-01-23 08:56:22
【问题描述】:
  • 我有一个尺寸为M x N 的图表。
  • 我也有两点[X,Y]
  • 和范围r

我们只能水平或垂直移动 1 个空格(我们总是必须移动)。我们的目标是找出我们可以达到的可能性的总数。

在此示例中,我们有 M=5N=4、[X=2Y=1] 和 r=3

我们可以看到,r 是一个奇数,所以我们正在搜索奇数。 结果显然是(将所有奇数相加后)12

我第一次尝试暴力破解,但太慢了 (O(n^2))。

所以我在数学上尝试了更多。我尝试了 冯诺依曼邻域 算法,但我完全被困在那里。最后,我通过计算xy对应的行中的垂直和水平计数来尝试它(x中的计数是3y中的计数是3)。

我的下一步是找到拐角并使用Taxicab geometry 计算到达那里需要多少rs。

然后我将图像分成 4 个部分(象限)。当[X, Y]到给定象限角的路径小于r时,只需将正方形的个数除以2即可。当路径较大时,我创建r_,由到角落的路径 rr_ = taxicab - r。然后从矩形的内容中减去r_,再除以2。

我们可以看到,结果也是正确的,3 + 3 + 1 + 1 + 2 + 2 = 12

但是

我在这里问:

  1. 我们仍在使用 偶数奇数 数,因此在将奇数除以 2 后经常会出现舍入错误。(例如 M=2, N=4, X=0, Y=0, r=4 - 见图. 有 8 个字段 - 3 个空白。但是 5/2 是 2.5 => 为什么取 3 而不是 2?我尝试添加不同的规则,但它因示例而异。)

  2. 对于这样的计算,是否有更有效的算法,同样快速且不易出错?

【问题讨论】:

    标签: algorithm matrix graph count von-neumann


    【解决方案1】:

    我们可以计算位于网格之外的方格,然后从范围内的奇数/偶数方格总数中减去这些方格(无论是否超出范围)。

    这实际上比我们想象的要容易。首先,我们希望能够计算范围内奇数/偶数方格的数量,而不考虑边界。使用直到 N - S = N * (N + 1) / 2 的自然数之和的公式,我们可以为此推导出几个简单的方程:

    def count_all(size):
        if size % 2:  # odd size
            return (size + 1) ** 2
        return size * (size + 2) + 1
    

    尝试自己推导这些可能是一个很好的练习,或者至少用几个例子来验证它们实际上是正确的。

    继续前进——我们可以通过“缩小”我们的半径来消除从上方掉出边界的点。这是非常直观的,所以让我给你一个图表。想象一下范围是某个固定数字的点,比如range=13,而我们的中心位于右下象限,比如(17, 5)。如果我们绘制这些点,用线将它们连接起来,它会创建一个菱形:

     |    /\
     |   /  \
     |  /    \
    -+-----------
     | /      \
     | \      /
     |  \    /
     |   \  /
     |    \/
    

    如果我们只关心计算轴上方的点,我们可以等效地只计算相应向上移动的较小钻石的轴上方的点。示例:

     |    /\
     |   /  \
     |  /    \
    -+-----------
     |  \    /
     |   \  /
     |    \/
    

    现在,这非常使用起来很方便,因为钻石的一半在上面,一半在下面。不过,我们有一半要小心——有些点落在轴上,两者都需要同等地考虑在边界内或边界外,但我们可以很容易地解释这一点。

    利用这种洞察力,我们可以通过缩小范围和移动中心点来计算跨轴超出范围的点数,并在这个新绘图的一半上计算点数。计数代码:

    def count_side(size):
        if size % 2:
            return (size + 1) // 2
        return size // 2 + 1
    
    def count_half(size):
        if size < 0:
            return 0
        return count_all(size) // 2 + count_side(size)
    

    请注意,我们必须注意偶数范围,因为我们需要只计算中心(范围 0)一次。

    虽然我们还没有完成——如果我们只是减去超出边界的点数,然后独立地向左移动,我们就会多算要删除的点数,因为我们计算的是左上象限两次。为了解决这个问题,我们使用相同的技巧。我们将首先在 x 轴上缩小 + 移动钻石,然后我们将在这个新钻石上再次这样做,但在 y 轴上。请注意,这颗新钻石最终将以原点为中心。我建议您在这一点上暂停片刻以想象这一点并说服自己这很好,实际上会为我们提供任何特定范围的新菱形,包含落在左上角的点数的 4 倍象限。

    使用它,我们计算左上象限中的点数,将它们重新添加到总数中。然后我们对右侧、底部和其他三个角重复相同的过程,以获得总体总数。整个解决方案如下:

    from itertools import product
    
    
    def count_all(size):
        if size % 2:
            return (size + 1) ** 2
        return size * (size + 2) + 1
    
    def count_side(size):
        if size % 2:
            return (size + 1) // 2
        return size // 2 + 1
    
    def count_half(size):
        if size < 0:
            return 0
        return count_all(size) // 2 + count_side(size)
    
    def count_quarter(size):
        if size < 0:
            return 0
        return count_all(size) // 4 + count_side(size)
    
    def get_deltas(pos, limit):
        return -(pos + 1), pos - (limit + 1)
    
    def count_inside(c, r, x, y, s):
        total = count_all(s)
    
        vertical_deltas   = get_deltas(x, c)
        horizontal_deltas = get_deltas(y, r)
    
        out_sides = sum(count_half(s + delta) 
            for delta in horizontal_deltas + vertical_deltas)
    
        out_corners = sum(count_quarter(s + delta_vert + delta_horiz)
            for delta_vert, delta_horiz in product(vertical_deltas, horizontal_deltas))
        
        inside = total - out_sides + out_corners
        return inside
    

    几个例子:

    >>> print(count_inside(5, 4, 2, 1, 3))
    12
    >>> print(count_inside(5, 4, 2, 1, 4))
    14
    >>> print(count_inside(5, 4, 2, 1, 5))
    15
    >>> print(count_inside(10, 6, 3, 2, 8))
    36
    

    【讨论】:

    • 谢谢你,这很聪明,但它不能按我需要的方式工作。我已经尝试但未能在我的代码中实现它,例如嵌入MN。另一件事是输入2, 1, 3 你得到16,但它应该是14。无论如何,感谢您的帮助。
    • @pingupingu for (2, 1, 3),(你在问题本身中的例子)你说答案应该是12,我手动计算了 12 个奇数点。对于 (2, 1, 4),我计算了 8 4、7 2 和 1 0,总共 16 个偶数平方。你能详细说明为什么这是错误的吗?
    • 因为在维度5x4 和点[2, 1] 中,会有6 四边形而不是8。其他四人出界。 (见。image
    • @pingupingu 对。我完全忘记了边界框的其余部分(M、N 约束)。更新了代码,现在应该可以工作了。
    • 非常感谢,你帮了我很多。看着它,这真的比我想象的要容易得多。不幸的是,我自己没有想到。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-11
    • 1970-01-01
    • 2019-11-22
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    相关资源
    最近更新 更多