私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「崩れないように積み上げて!」(CodeIQ)を参照してください。
直方体の箱を重ねて置くことを考えます。
ただし、上の箱は下の箱よりも小さくないと、崩れてしまう可能性があります。
そこで、大きな箱の上に小さな箱を置くことを考えます。
ここで「小さい」とは縦と横の長さがともに短いことにします。
つまり、中央に重ねて置くと、上から見たときに下の箱の輪郭が見えるようにします。
また、箱は縦か横方向に綺麗に置くものとし、斜めに傾けて置くことはありません。
m 個の箱を用意したとき、その上面の面積の和が n でした。
このような箱のサイズの組み合わせが何通りあるか求めてください。
ただし、辺の長さはいずれも正の整数とし、箱の高さは考えないものとします。
なお、同じサイズの箱でも縦と横の置き方が違う場合は別々にカウントします。
例えば、m = 3, n = 20 のとき、上から見ると以下の4通りがあります。
同様に、m = 2, n = 14 のとき、上から見ると以下の8通りがあります。
標準入力から m, n が与えられたとき、その箱のサイズの組み合わせを求め、その数を標準出力に出力してください。
なお、m, n は 0 < m < n < 250を満たす整数とします。
【入出力サンプル】
標準入力
3 20
標準出力
4
Rubyで解答しています。
#!/usr/bin/ruby $Memo = {} def getNext(m, n, x, y) # printf("m=%d, n=%d, x=%d, y=%d\n", m, n, x, y) return $Memo[[m,n,x,y]] if $Memo[[m,n,x,y]] != nil if m == 0 then if n == 0 then return 1 else return 0 end end ret = 0 st = (m>1 ? 2 : 1) for nx in st...x for ny in st...y break if n < nx*ny ret += getNext(m-1, n-nx*ny, nx, ny) end end $Memo[[m,n,x,y]] = ret $Memo[[m,n,y,x]] = ret if x != y return ret end def solve(m, n) ret = 0 st = (m>1 ? 2 : 1) for x in st..n y = st while x*y <= n ret += getNext(m-1, n-x*y, x, y) y += 1 end end return ret end # main while line = gets line.strip! next if line.empty? m,n = line.split.map{|a| a.to_i} p solve(m,n) end
入力値を数値にしてsolve()に渡し、結果を印字します。
一番下の箱はその下の箱が無く、縦横の辺の長さの制約が無いのでこれだけsolve()で列挙しています(よく考えたら特別扱いの必要はありませんでした。雑感に書きます)。
retは答えの数です。
stは辺の最小値でm=1の時は1を最小、それ以外の場合一番下の箱の辺に1はありえないので2以上にしています(これももっとうまくできます。雑感で書きます)。
後は横xと縦yの長さを任意に決め、その積がn未満の間getNext()で上の箱を積む処理を行います。getNext()は積めるパターン数を返すのでretに加算します。
最終的にretを返却します。
mは後何回箱を積むかです。
nは残りm回箱を積んだ時、その箱の面積の合計です。
x,yは一つ下の箱の横と縦の長さです。
$Memoにパターンがあれば計算済みなので$Memoの値を返します。
mが0の時、nが0ならOKのパターンなので1を返します。nが0でなければNGなので0を返します。
stは最小の辺の長さでm=1の時以外は最小2、m=1の時は最小1とします。
後はst以上でy、x未満の範囲で任意に縦横の長さを決め、再帰的にgetNext()を呼び出します。
ret返却前に$Memoに結果をメモしますが、x,yを入れ替えたパターンも記録します。
まず、一番下の箱だけを特別扱いしましたが、よく考えたらnを辺の長さの上限にできたのでgetNext()と一つにできました。
後、最小の辺の長さは単純にmにできました。例えば3段積む時、一番上の箱の最小は1*1なので一番下は3*3以上でなければなりません。
もう少し考えればよかった、というより、コードを書いている時には何でこのくらいのことに気がつかなかったんだろう?