問題
解答
グリッド上の座標を(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
返信削除組合せの問題として考えるといいですよ。
あぁそうか!参考になります。
返信削除組み合わせや順列は一等苦手なんですが、こういう問題にはそのまま使えるんですね。復習しなければ。