問題
解答
グリッド上の座標を(x, y)で表すこととし、左上の始点を(0, 0)、右下の終点を(20, 20)と定めます。 座標(x, y)から(20, 20)へのルート数をroutesFrom(x, y)と書くことにすると、この問題はroutesFrom(0, 0)を求める問題に相当します。 routesFrom(x, y)は次のようにして求められます:
- routesFrom(x, y) = 1 (x = 20, y = 20)
- routesFrom(x, y) = 0 (x > 20 or y > 20)
- routesFrom(x, y) = routesFrom(x + 1, y) + routesFrom(x, y + 1) (otherwise)
routesFrom(0, 0)の計算量はグリッドの大きさnの増加に対応して急激に大きくなります(多分計算量の上限はO(2^n)くらい)。 routesFrom(0, 0) = routesFrom(1, 0) + routesFrom(0, 1) = routesFrom(2, 0) + routesFrom(1, 1) + routesFrom(1, 1) + routesFrom(0, 2) = ...といったように、基底ケース(上の1.と2.)に行き当たるまで倍々に項が増えていきます。今回はn=20なので、このままでは現実的な時間内に終わりません。
別々のルートが同じ座標を通ることはよくあります。先ほどの展開でroutesFrom(1, 1)という項が2回出てきているのは、(0, 0)から(1, 1)に至るルートが2つ存在するからです。 その座標に至るまでのルートに関わらずそれ以降に取り得るルートの数は同じなので、routesFrom(x, y)の値をメモ化することで膨大な計算を省くことができます。
(use srfi-1)
(define (count-routes num-cells)
  (define lookup-table (make-vector (+ num-cells 1)))
  (define (routes-from x y) (ref (ref lookup-table y) x))
  (define (count-routes-1 x y)
    (cond
     ((or (> x num-cells) (> y num-cells)) 0)
     ((and (= x num-cells) (= y num-cells)) 1)
     ((not (zero? (routes-from x y))) (routes-from x y))
     (else
      (let ((num-routes (+ (count-routes-1 (+ x 1) y)
                           (count-routes-1 x (+ y 1)))))
        (set! (routes-from x y) num-routes)
        num-routes))))
  (set! (setter routes-from)
        (lambda (x y num-routes) (set! (ref (ref lookup-table y) x) num-routes)))
  (for-each (lambda (i)
              (set! (ref lookup-table i) (make-vector (+ num-cells 1) 0)))
            (iota (+ num-cells 1)))
  (count-routes-1 0 0))
(define (solve) (count-routes 20))
(define (main argv)
  (display (solve))
  (newline))
40C20 = 137846528820
返信削除組合せの問題として考えるといいですよ。
あぁそうか!参考になります。
返信削除組み合わせや順列は一等苦手なんですが、こういう問題にはそのまま使えるんですね。復習しなければ。