「今週のお題:綺麗に並んだセミナーの座席」問題解答 ( CodeIQ )
はじめに
CodeIQという所で週次で出題される「今週のお題」シリーズに解答しましたので、ネタバレです。
問題
ちょっと条件がたくさんあってややこしいです。まとめてみると、
- 全部で n 席 ( 個々の席は区別しない ) を並べる方法が何通りかを出力する。n は標準入力として与えられる。
- 席は、単数/複数のブロックを、縦に通じる路を挟んで配置する。横の通路は設けない。
- 各ブロックは、横2~5席、縦2~(任意)席の長方形の配置とする。縦の席数は全ブロック共通。
- 全体が横方向に関して対称の配置にする。
という、組み合わせ問題です。
※2015/11/5追記:記事が公開されましたので追記。あと細かいところ修正。
解答
golf解として、以下のRuby(115)を提出しました。1tweetに収めることはできますが、あまり短くはなりませんでした。
n=eval *$< h=[a=0,1,0,1,1,2] 2.upto(n.times{h<<h[-1]+h[-2]-h[-6]}/2){|w|n%w<1&&a+=(~w&1)*h[1+w/=2]+h[w]+h[w-1]} p a
解説
以下、提出時のコメントに追記してまとめたものです。( 1度書いておくとラクできますね )
方針
入力 n に対し、 と積 ( wが幅、dが奥行 ) に分解した時の配置の数は、
dに関わらず wによって一意に決まります。これを f(w) とし、f(w) を求めることを考えます。
※最終的な答えは、各分解に対する f(w) の合計で求まります。
f(w)について
f(w) については、w の偶奇によって分かれます。が、いずれにしても中央の席のブロックに対し、 対称たる左右にどのような幅で席の配置を行うか、が焦点です。
- w が偶数の場合は、中央が幅 0,2,4 のブロックに対し、左右の幅が 席ずつ
- w が奇数の場合は、中央が幅 3,5 のブロックに対し、左右の幅が 席ずつ
というケースがあります。
※w が偶数の場合、対称軸を通路とする配置も考える必要があるのですが、それを「幅0のブロック」とみなすことで、他と同じように扱える訳です。
そのため、この左右の幅 ( kとする ) に対応する席の配置 g(k) を考えます。
左右どちらかの配置が決まればもう片方も自動的に決まるので、
という関係が成り立ちます。ここで、/ は整数除算、すなわち小数点以下は切り捨てです。こうすることで、偶数・奇数での式の見た目がかなり似てきます。
なお、便宜上、0個の席の配置は1通り、負数の席の配置は0通りとして扱います。
※負数については、 なので、-1 のみ考えれば十分です。
g(k)について
さて、ブロックは幅2~5となっているので、
という隣接6項間漸化式が成り立ちます。
実際には項数を減らすため、
という隣接7項間漸化式にして使います。( 文字数節約以上の意味はありませんが )
なお、g の初期値は次のようになります。
コード化
これで材料は全て出揃いました。
なお、g の添え字が -1 からなのはコード上扱いにくいので、
なる h を設け、最初に十分な ( 過剰ながら ) 数の項を配列として蓄えておきます。
※hの満たす漸化式は、もちろんgと同じになります。
wの偶奇の違いは、 に対する係数で調節します ( もちろん if文で分岐させても良いんですけど )。
最終的に、
となります。
という訳で、素直に組んだコードの例は以下の通りになります。冒頭のコードはこれを色々縮めたものです。
※2015/11/5追記:コードに誤植 ( ?不足 ) があったので修正。
n=gets.to_i a=0 h=[0,1,0,1,1,2] n.times{ h<<h[-1]+h[-2]-h[-6] } 2.upto(n/2){|w| if n%w==0 coef=w.even? ? 1 : 0 a+=coef*h[w/2+1]+h[w/2]+h[w/2-1] end } puts a
終わりに
最終的に漸化式の形に書いてしまえば、そこからコードに落とすのはラクなので、如何に整理して漸化式を割り出すか。そこが楽しい所ですね。
あ。速度については、いずれにせよΟ(n) なので ( というか、n が大きくなると解が発散しちゃうので )、特に頑張ってはいません。