有没有人见过类似的编程拼图?

时间:2009-05-27 01:37:29

标签: language-agnostic dynamic-programming

“假设您想要从4×1和6×1乐高积木的行中构建实体面板。对于结构强度,块之间的空间必须永远不会排列在相邻的行中。例如,18×下面显示的3个面板是不可接受的,因为顶部两行中的块之间的空格对齐。

有两种方法可以构建10×1面板,2种构建10×2面板的方法,8种构建18×3面板的方法,以及7958种构建36×5面板的方法。

有多少种方法可以构建64×10面板?答案将适合64位有符号整数。编写程序来计算答案。你的程序应该运行得非常快 - 当然,它不应该超过一分钟,即使在较旧的机器上也是如此。让我们知道您的程序计算的价值,程序计算该值的时间,以及您运行它的机器类型。将程序的源代码作为附件包含在内。 “

我最近得到了一个编程谜题,并且一直绞尽脑汁试图解决它。我用c ++编写了一些代码,我知道这个数字是巨大的...我的程序运行了几个小时才决定停止它,因为即使在慢速计算机上也需要1分钟的运行时间。有没有人见过类似这样的谜题?已经有几个星期了,我再也不能把它拿出去了,但这真的让我感到不安,因为我无法正确解决它。有关使用算法的任何建议吗?或者也许可以通过“开箱即用”来解决它的方法。我使用的是制作一个程序,构建4x1和6x1块的每个可能的“层”,以形成64x1层。结果是大约3300个不同的层。然后我让我的程序运行并将它们堆叠到所有可能的10层高墙中,没有裂缝排列......正如你可以看到这个解决方案需要很长很长时间。因此,在时间限制内,蛮力似乎无法有效地解决这个问题。任何建议/见解将不胜感激。

3 个答案:

答案 0 :(得分:5)

主要的见解是:在确定第3行中的内容时,您不关心第1行中的内容,而只关注第2行中的内容。

因此,让我们调用如何将64x1层构建为“行场景”。你说有大约3300行场景。那不是那么糟糕。

让我们计算一个函数:

f(s,r)=将行方案编号“s”放入行“r”的方法的数量,并合法地填充“r”上方的所有行。

(我指的是顶部的行“1”,底部的行“10”)

如果您想要避免掠夺者,请立即停止阅读。

现在很清楚(将行数从1编号到10):

f(s,1)= 1

表示“s”的所有值。

此外,这也是洞察力的来源,(使用 Mathematica -ish表示法)

f(s, r) = Sum[ f(i, r-1) * fits(s, i) , {i, 1, 3328} ]

其中“fits”是一个函数,它接受两个场景数字,如果你可以合法地将这两行叠加在一起,则返回“1”,如果不能,则返回“0”。这使用了洞察力,因为放置场景的合法方式的数量仅取决于根据“适合”在其上方放置场景的方式的数量。

现在,拟合可以预先计算并存储在3328 x 3328字节数组中。那只是大约10兆的内存。 (如果你喜欢它并将它存储为一个阵列,那就少了)

答案显然只是

Sum[ f(i, 10) , {i, 1, 3328} ]

答案 1 :(得分:4)

这是我的答案。它是Haskell,除其他外,你可以免费获得bignums。

编辑:它现在实际上在合理的时间内解决了这个问题。

更多编辑:使用稀疏矩阵,我的计算机需要半秒钟。

您计算每种可能的方式来平铺一行。假设有N种方法可以平铺一行。制作NxN矩阵。如果行i可以出现在行j旁边,则元素i,j为1,否则为0。从包含N 1的向量开始。将矩阵乘以向量的次数等于墙的高度减1,然后对得到的向量求和。

module Main where
import Data.Array.Unboxed
import Data.List
import System.Environment
import Text.Printf
import qualified Data.Foldable as F
import Data.Word
import Data.Bits

-- This records the index of the holes in a bit field
type Row = Word64

-- This generates the possible rows for given block sizes and row length
genRows :: [Int] -> Int -> [Row]
genRows xs n = map (permToRow 0 1) $ concatMap comboPerms $ combos xs n
  where
    combos [] 0 = return []
    combos [] _ = [] -- failure
    combos (x:xs) n =
      do c <- [0..(n `div` x)]
         rest <- combos xs (n - x*c)
         return (if c > 0 then (x, c):rest else rest)
    comboPerms [] = return []
    comboPerms bs =
      do (b, brest) <- choose bs
         rest <- comboPerms brest
         return (b:rest)
    choose bs = map (\(x, _) -> (x, remove x bs)) bs
    remove x (bc@(y, c):bs) =
      if x == y
         then if c > 1
                 then (x, c - 1):bs
                 else bs
         else bc:(remove x bs)
    remove _ [] = error "no item to remove"
    permToRow a _ [] = a
    permToRow a _ [_] = a
    permToRow a n (c:cs) =
      permToRow (a .|. m) m cs where m = n `shiftL` c

-- Test if two rows of blocks are compatible
-- i.e. they do not have a hole in common
rowCompat :: Row -> Row -> Bool
rowCompat x y = x .&. y == 0

-- It's a sparse matrix with boolean entries
type Matrix = Array Int [Int]
type Vector = UArray Int Word64

-- Creates a matrix of row compatibilities
compatMatrix :: [Row] -> Matrix
compatMatrix rows = listArray (1, n) $ map elts [1..n] where
  elts :: Int -> [Int]
  elts i = [j | j <- [1..n], rowCompat (arows ! i) (arows ! j)]
  arows = listArray (1, n) rows :: UArray Int Row
  n = length rows

-- Multiply matrix by vector, O(N^2)
mulMatVec :: Matrix -> Vector -> Vector
mulMatVec m v = array (bounds v)
    [(i, sum [v ! j | j <- m ! i]) | i <- [1..n]]
  where n = snd $ bounds v

initVec :: Int -> Vector
initVec n = array (1, n) $ zip [1..n] (repeat 1)

main = do
  args <- getArgs
  if length args < 3
    then putStrLn "usage: blocks WIDTH HEIGHT [BLOCKSIZE...]"
    else do
      let (width:height:sizes) = map read args :: [Int]
      printf "Width: %i\nHeight %i\nBlock lengths: %s\n" width height
             $ intercalate ", " $ map show sizes
      let rows = genRows sizes width
      let rowc = length rows
      printf "Row tilings: %i\n" rowc
      if null rows
        then return ()
        else do
          let m = compatMatrix rows
          printf "Matrix density: %i/%i\n"
                 (sum (map length (elems m))) (rowc^2)
          printf "Wall tilings: %i\n" $ sum $ elems
                  $ iterate (mulMatVec m) (initVec (length rows))
                            !! (height - 1)

结果......

$ time ./a.out 64 10 4 6
Width: 64
Height 10
Block lengths: 4, 6
Row tilings: 3329
Matrix density: 37120/11082241
Wall tilings: 806844323190414

real    0m0.451s
user    0m0.423s
sys     0m0.012s

好的,500毫秒,我可以忍受。

答案 2 :(得分:1)

我为一个编程比赛解决了类似的问题,这个比赛用各种形状的瓷砖铺设了一条长长的走廊。我使用动态编程:给定任何面板,有一种方法可以通过一次放置一行来构造它。每行在其末端可以具有有限多个形状。因此,对于每个行数,对于每个形状,我计算制作该行的方式。 (对于底行,只有一种方法可以制作每个形状。)然后每行的形状决定下一行可以采用的形状数(即从不排列空格)。这个数字对于每一行都是有限的,事实上因为你只有两种尺寸的砖,它会很小。因此,您最终会花费每行不间断的时间,并且程序会很快完成。

要表示一个形状我只会列出4和6的列表,然后使用此列表作为表中的键来存储在行 i 中制作该形状的方法的数量,每个