具有挑战性的动态规划问题。

23
这是一个我需要解决的计算机视觉问题的简化版本。假设给定参数n和q,必须计算出将整数0..(q-1)分配给n×n网格元素的方法数,使得对于每个分配,以下情况均为真:
  1. 相邻的两个元素(水平或垂直)没有相同的值。
  2. 位置(i,j)的值为0
  3. 位置(k,l)的值为0
由于未知(i,j,k,l),输出应该是上述评估的数组,对于每个有效设置的(i,j,k,l)都有一个。
下面是一种暴力方法。目标是获得一种有效的算法,适用于q≤100,n≤18的情况。
def tuples(n,q):
  return [[a,]+b for a in range(q) for b in tuples(n-1,q)] if n>1 else [[a] for a in range(q)]

def isvalid(t,n):
  grid=[t[n*i:n*(i+1)] for i in range(n)];
  for r in range(n):
    for c in range(n):
      v=grid[r][c]
      left=grid[r][c-1] if c>0 else -1
      right=grid[r][c-1] if c<n-1 else -1
      top=grid[r-1][c] if r > 0 else -1
      bottom=grid[r+1][c] if r < n-1 else -1
      if v==left or v==right or v==top or v==bottom:
        return False
  return True

def count(n,q):
  result=[]
  for pos1 in range(n**2):
    for pos2 in range(n**2):
      total=0
      for t in tuples(n**2,q):
        if t[pos1]==0 and t[pos2]==0 and isvalid(t,n):
          total+=1

      result.append(total)

  return result

assert count(2,2)==[1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1]

11/11更新 我也在TopCoder 论坛上提出了这个问题,他们的解决方案是目前为止最有效的(作者估计n=10时,任何q都只需要大约3小时)。


5
如果您在您的代码前缩进四个空格,它将被格式化。编辑器上方有一个“代码示例”按钮,您可以使用它。只需选中您的代码,然后点击带有二进制数字的按钮即可。 - Bill the Lizard
2
我不理解 对于每个 i、j、k、l 的组合,位置 (i,j) 和 (k,l) 上的值都为 0 - Loïc Février
7
Eric Lippert最近在他的博客上写了一系列有关这个话题的文章。第一篇 第二篇 第三篇 第四篇 第五篇 - Daniel Pryden
2
Loic和Belisarius:是的,我正在将这两个位置的颜色修正为零。由于(i,j,k,l)没有给出,我必须迭代每个(i,j,k,l)组合,并给出如果提供了特定的i,j,k,l组合,将得到的着色数量。 - Yaroslav Bulatov
2
@Yaroslav:使用Eric Lippert的算法,你可以轻松地通过将找到的每种着色方案作为“失败”来计算着色数量。当然,这基本上是一种蛮力解决方法。尽管如此,几乎任何你想出来的找到着色方案的解决方案都会涉及猜测阶段,在这样的算法中计数着色方案也很容易,只需在每次找到一种着色方案时强制进行回溯即可。 - Brian
显示剩余13条评论
6个回答

3
也许这听起来太简单了,但它是有效的。随机分配值到所有单元格,直到只有两个单元格为空。测试所有值的相邻性。计算成功投掷的百分比与所有投掷的平均值,直到方差降到可接受的范围内。
风险降至零,而且所冒的风险仅是少量运行时间。

对于大型案例的思路非常出色。我会添加一些处理微不足道的情况,比如q=2,即组合数很少且这种估计将不准确,但除此之外非常好。 - Phil H
@Yaroslav,你对近似算法感兴趣吗?无论如何,@Harryooo 的想法很聪明。关于实现给定边际所需的尝试次数的O(多少次尝试)的任何分析吗? - Dave Aaron Smith
是的,我对精确结果很感兴趣。实际上,我想要精确结果的原因是为了能够测试像Harry这样的近似算法,并将其与小n的精确结果进行比较。 - Yaroslav Bulatov
3
当n=10,q=3时,存在约10^20种合适的分配方式和10^47种总的分配方式,因此使用这种方法需要大约进行10^27次调用才能获得一个成功的结果。 - Yaroslav Bulatov

2

这不是答案,只是对讨论的贡献,因为评论框太小了。

简而言之,任何算法都不能简化为“计算可能性并计数”,例如Eric Lippert的算法或暴力方法,都无法满足@Yaroslav的目标,即q <= 100n <= 18

让我们首先考虑一个单独的n x 1列。有多少个有效编号存在?对于第一个单元格,我们可以选择q个数字。由于我们不能垂直重复,因此我们可以在第二个单元格中选择q - 1个数字,因此在第三个单元格中也可以选择q - 1个数字,以此类推。对于q == 100n == 18,这意味着有q * (q - 1) ^ (n - 1) = 100 * 99 ^ 17种有效的着色方式,大约为10 ^ 36

现在考虑任意两个有效列(称为面包列),它们之间有一个缓冲列(称为芥末列)。当q >= 4时,这里有一种简单的算法可以找到芥末列的有效值集。从芥末列的顶部单元格开始。我们只需要考虑面包列的相邻单元格,其中最多有2个唯一值。为芥末列选择任何第三个数字。考虑芥末列的第二个单元格。我们必须考虑先前的芥末单元格和具有最多3个唯一值的2个相邻面包单元格。选择第4个值。继续填写芥末列。

我们最多有2列包含硬编码单元格0。使用芥末列,因此我们可以制作至少6个面包列,每个面包列大约有10 ^ 36种解决方案,总共至少有10 ^ 216种有效解决方案,根据舍入误差的数量而定。

根据维基百科,宇宙中大约有10 ^ 80个原子。

因此,要更聪明。


1
更新11/11:我也在TopCoder论坛上提出了这个问题,他们的解决方案是我目前看到的最有效的解决方案(根据作者的估计,n=10,任何q,大约需要41小时)。
我是作者。不是41个小时,只需要3个令人尴尬的可并行CPU小时。我已经计算了对称性。对于n=10,只有675个真正不同的(i,j)和(k,l)对。我的程序每个对需要大约16秒。

0

我正在基于Dave Aaron Smith的讨论建立一个贡献。

现在先不考虑最后两个限制条件((i,j)(k,l))。

只有一列(nx1)时,解决方案为q * (q - 1) ^ (n - 1)


第二列有多少选择?对于顶部单元格(1,2),有(q-1)种选择,但是对于单元格(2,2),如果(1,2)/(2,1)具有相同的颜色,则有q-1q-2种选择。

对于(3,2)也是如此:q-1q-2种解决方案。

我们可以看到,我们有一个可能性的二叉树,我们需要对该树进行求和。假设左子节点始终为“顶部和左侧颜色相同”,右子节点为“不同颜色”。

通过计算树上左列创建这样的配置的可能性以及我们正在着色的新单元格的可能性数量,我们将计算出着色两列的可能性数量。

但现在让我们考虑第二列的着色概率分布:如果我们想要迭代这个过程,我们需要在第二列上有一个均匀分布,就像第一列从未存在过一样,在所有第一列和第二列的着色中,我们可以说其中1/q的着色在第二列的顶部单元格中为0。

没有均匀分布是不可能的。

问题是:分布是否均匀?

答案: 我们通过先构建第二列,然后是第一列,最后是第三列来获得相同数量的解。在这种情况下,第二列的分布是均匀的,因此在第一种情况下也是如此。

现在我们可以将同样的“树形思想”应用于计算第三列的可能性数量。

我将尝试发展出一个通用公式(由于树的大小为2^n,我们不想显式地探索它)。


我不太确定我是否理解了这个问题,但是计算着色的标准列方法是使用一个(q^n)x(q^n)矩阵A,其中ij项给出1当且仅当第2列的第j种着色与第1列的第i种着色兼容。然后,A^(n-1)元素之和就是适当着色的数量。 - Yaroslav Bulatov
@Yaroslav Bulatov:我不知道这个方法,但它的复杂度似乎至少为(q^n)^2。我正在尝试找到一个可以在O(q*n)内计算的公式。 - Loïc Février

0

以下是一些可能对其他回答者有帮助的观察:

  1. 值1..q是可互换的 - 它们可以是字母,结果将是相同的。
  2. 不能让相邻的单元格匹配的约束条件非常温和,因此暴力方法会过于昂贵。即使您知道除一个单元格外的所有值,对于q>8,仍将至少有q-8个可能性。
  3. 这个输出将非常长 - 每组i、j、k、l都需要一行。组合数大约为n2(n2-3),因为两个固定的零可以放在任何地方,除非它们不需要遵守第一个规则。对于n=100和q=18的最难情况,这是 ~ 1004 = 1亿。因此,这是您的最小复杂度,并且由于问题当前陈述的方式,是不可避免的。
  4. 有简单的情况 - 当q=2时,有两种可能的棋盘,因此对于任何给定的零对,答案都是1。

第三点将整个程序的最小时间复杂度设为O(n2(n2-3)),并且建议您需要为每对零编写一些相当有效的计算,因为仅仅写入1亿行而没有任何计算会花费一段时间。参考数据:每秒钟1行,即1x108s约等于3年,或在12核心计算机上为3个月。

我怀疑对于一对零有一个优雅的答案,但我不确定是否有解析解。鉴于您可以根据零的位置使用2或3种颜色进行操作,您可以将地图分成一系列区域,每个区域只使用2或3种颜色,然后就是每个区域中qC2或qC3(2或3的组合数)的不同组合数量乘以区域数量,再乘以分割地图的方式数量。


对于n=18,输出大约是10万个值,而不是1亿个。 - Yaroslav Bulatov

0

我不是数学家,但我认为这个问题应该有一个解析解决方案,即:

首先,计算NxN棋盘使用Q种颜色(包括邻居,定义为具有共同边缘的不得相同颜色)可能出现的不同着色数量。这应该是一个非常简单的公式。

然后找出这些解中有多少在(i,j)处为0,这应该是1/Q的分数。

然后根据曼哈顿距离| i-k | + | j-l |以及到棋盘边缘的距离和这些距离的“奇偶性”,例如可被2整除、可被3整除、可被Q整除,找出其余解中有多少在(k,l)处为0。

最后一部分是最难的,但如果您真的擅长数学,我认为它仍然是可行的。


实际上,对于着色数量并没有一个简单的公式 -- 它也被称为色多项式,这里是一个13x13网格的版本 -- http://www.staff.hs-mittweida.de/~peter/research/results/chrom_poly_g13x13.txt - Yaroslav Bulatov
我最初考虑的是无限晶格,以及边缘和感应零点的位错。50年前,晶体学在纸上就处理了这个问题;-) - Dima Tisnek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接