在棋盘中构造阻挡集,从中禁止矩形

时间:2017-12-20 15:10:45

标签: algorithm dynamic-programming

假设我们有一个带有 m * n 单位正方形的棋盘。考虑其中 a * b 形状的矩形多联骨牌,其中 a 小于或等于 m b 小于或等于 n 。如果任何此类多边形(及其旋转)与至少一个单元相交,则单元正方形 S 的子集称为(a,b)类型的阻塞集 S 中的正方形。现在我想找到一个算法来构造一个具有最小单位平方数的阻塞集。

我对MSE中最小阻塞集大小的封闭公式提出了同样的问题,希望这次解决方案更容易。

虽然现在已经有了一些答案,但我认为我有责任使问题更清楚。让我举一个具体的例子。假设我们的棋盘大小 6 * 6 。在这个momemt让我们考虑案例 a = 2,b = 3 。例如,下图中的黄色块是两个 2 * 3 矩形多面体。

rectangular polyominoes

现在考虑下面的红色单位正方形的 S

a blocking set

很容易检查板上的任何 2 * 3 矩形多边形都必须与 S 相交,并且根据鸽笼原理,任何阻挡组必须至少包括6个单位正方形,因此 S 也是最小的。当然, S 并不是唯一的,因为人们可以找到另一个与它不相同的最小阻塞集。确切地说,我正在寻找的是一种在板上构建一个最小阻塞集的算法,就像上面的红色集 S 一样,但是一般四倍(< EM> M,N,A,b )。我希望这个解释有效。

2 个答案:

答案 0 :(得分:0)

标准的动态编程解决方案是通过配置文件超过此行来保持一组最低成本。&#34;不幸的是,有max(a, b)^m个这样的配置文件是可能的,所以它不会很好地扩展到大矩形。

然而,作为一种简化,我们可以尽可能地向上和向下滑动每个矩形。这确实减少了一些配置文件的数量。

说到这里,这里运行Python代码来解决这个问题。但请注意,即使发现blocking(11,10, 4,3)120这样简单的事情也需要一些时间才能运行。这是因为它必须跟踪多少数据,以及它创造了多少临时垃圾。

def blocking (m, n, a, b):
    # Small optimization.
    if (m < n):
        (m, n) = (n, m)
    # We start before with a row with nothing sticking up, of cost 0 to get here.
    last_solutions = {
        tuple([0 for i in range(m)]): 0
        }
    # Now advance one row, n times.
    for i in range(n):
        next_solutions = {}
        for (profile, cost) in last_solutions.items():
            carried_profile = tuple([i-1 for i in profile])
            for (new_profile, incremental_cost) in advance_row(carried_profile, a, b):
                if tuple(reversed(new_profile)) < new_profile:
                    # By symmetry we only need to look at one of these.  Look at the other!
                    pass
                elif new_profile in next_solutions:
                    next_solutions[new_profile] = min(next_solutions[new_profile], cost + incremental_cost)
                else:
                    next_solutions[new_profile] = cost + incremental_cost
        last_solutions = next_solutions
    return min(last_solutions.values())

def advance_row(profile, a, b, p=0):
    if len(profile) <= p:
        # We reached the end.  Yield it.
        yield (profile, 0)
    else:
        if 0 < profile[p]:
            # It is OK to put no block here.
            for (next_profile, cost) in advance_row(profile, a, b, p+1):
                yield (next_profile, cost)
        area = a * b
        if profile[p] < b:
            # We might place a block a x b here
            new_profile = add_in_rectangle(profile, a, b, p)
            for (next_profile, cost) in advance_row(new_profile, a, b, p+1):
                yield (next_profile, cost+area)
        if profile[b] < b:
            # We might place a block b x a here
            new_profile = add_in_rectangle(profile, b, a, p)
            for (next_profile, cost) in advance_row(new_profile, a, b, p+1):
                yield (next_profile, cost+area)

# Creates a copy of the profile with a block of length a, height b
# filled in starting at position p
def add_in_rectangle(profile, a, b, p):
    # Make a copy
    new_profile = [x for x in profile]
    for i in range(p, min(p + b, len(profile))):
        new_profile[i] = max(profile[i], a)
    return tuple(new_profile)

答案 1 :(得分:0)

这是第二种可能快得多的方法,但我不会花时间对其进行编码。但是因为它与我的其他解决方案无关,所以我会将其作为一个单独的答案。

将局部倾斜的轮廓集视为图的节点。从每一个中你可以构造一组边,这是添加一个矩形的结果,该矩形的底边覆盖了未完全填充的底行上最左边的空方块。(这将给a + b个边缘每个节点。)

现在从节点构造一个A*搜索,该搜索对于完全填充的节点没有任何意义。从任何节点,估计成本是a*b的最小倍数,大于空方格的数量。

如果经常出现这种情况,通过贪婪的搜索可以产生最佳的平铺,这种搜索将很快发现并给你一个答案。如果不是这样,它只需要回溯。

即使它确实回溯,你跟踪你去过哪个节点的事实将为你提供DP解决方案的好处,可能会更好地修剪你必须分析的可能性。