私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「回文数の中央値」(CodeIQ)を参照してください。
【概要】
数の範囲を指定します。
その範囲内にある回文数( seeWikipedia - 回文数) の、真ん中の値を計算してください。
例えば下表の通りです:
値の範囲 回文数 真ん中の値 99〜120 99, 101, 111 101 200〜232 202, 212, 222, 232 212, 222
【入出力】
入力は
99,120
のようになっています。
下限と上限がコンマ区切りで並んでいます。
下限以上で、上限以下の回文数の真ん中の値を求めてください。
出力も普通に10進数です。
範囲内にある回文数が奇数個の場合には、中央の値はひとつに決まるのでそれを。
偶数個の場合にはぴったり中央の値はありませんので、中央付近の2つの値をコンマ区切りで昇順に出力してください。
ただし、範囲内に回文数がひとつもない場合は
-
を出力してください。
【例】
入力 範囲内の回文数 状況 99,120 99, 101, 111 101 200,232 202, 212, 222, 232 212,222 123,124 - 999,1221 999, 1001, 1111, 1221 1001,1111 1234,1400 1331 1331
【補足】
不正な入力に対処する必要はありません。
0 < 下限 < 上限 ≦ 一千兆 です。
Rubyで解答しています。
#!/usr/bin/ruby # 桁数 0 一 十 百 千 万 十万 百万 千万 一億 十億 百億 千億 一兆 十兆 百兆 Counts = [0, 10, 19, 109, 199, 1099, 1999, 10999, 19999, 109999, 199999, 1099999, 1999999, 10999999, 19999999, 109999999] # 回分数かどうかチェックする def isKaibun(n) s = n.to_s r = s.size-1 for i in 0..(r/2) # printf("%s, %s\n", s[i], s[r-i]) return false if s[i] != s[r-i] end return true end # r桁のn番目の回分数を求める def getKaibun(n, r) return n-1 if r == 1 s = (n-1).to_s if (r % 2) == 0 then s += s.reverse else s += s[0..-2].reverse end while s.size < r s = '0' + s + '0' end return s.to_i + 10**(r-1) + 1 end # 引数以下の最大の回分数がその桁の何番目かを返す # 0は1番目 def getIndexOfKaibun(n) r = n.to_s.size return n+1 if r == 1 base = 10**(r-1) + 1 ret = (n - base) / (10**(r/2)) loop{ v = getKaibun(ret+1, r) # printf("n=%d, v=%d, ret=%d\n", n, v, ret) if v <= n then break else ret -= 1 end } return ret + 1 end # 何桁の何番目の回分数かを求める def search(idx) for i in 1...Counts.size return [i, idx-Counts[i-1]] if (Counts[i-1] < idx) && (idx <= Counts[i]) end end # 問題を解く def solve(mn, mx) mx = 999999999999999 if mx > 999999999999999 # 入力値の範囲にある回分数の最小と最大のインデックスを決める r_min = mn.to_s.size i_min_of_r = getIndexOfKaibun(mn) r_max = mx.to_s.size i_max_of_r = getIndexOfKaibun(mx) # 全体で何番目か? i_min = Counts[r_min-1] + i_min_of_r i_max = Counts[r_max-1] + i_max_of_r # 範囲内に値が無い return "-" if i_max < i_min # printf("i_min_of_r=%d, i_max_of_r=%d, i_min=%d, i_max=%d\n", i_min_of_r, i_max_of_r, i_min, i_max) # p getKaibun(i_min_of_r, r_min) # p getKaibun(i_max_of_r, r_max) # 範囲内の個数を求める if isKaibun(mn) then # mnが回分数の場合、mnが範囲内に含まれる cnt = i_max - i_min + 1 if (cnt) % 2 == 1 then target = [(i_max + i_min)/2] else target = [(i_max + i_min)/2, (i_max + i_min)/2+1] end # printf("mn=Kaibun: cnt=%d target=%s\n", cnt, target.to_s) else # mnが回分数でない場合、mnは範囲に含まれない cnt = i_max - i_min if (cnt) % 2 == 1 then target = [(i_max + i_min)/2+1] else target = [(i_max + i_min)/2, (i_max + i_min)/2+1] end # printf("mn!=Kaibun: cnt=%d target=%s\n", cnt, target.to_s) end ret = [] for t in target r, pos = search(t) # printf("r=%d, pos=%s\n", r, pos) ret << getKaibun(pos, r) end if (ret.size == 2) && ((ret[0] < mn) || (mx < ret[1])) then return '-' elsif (ret.size == 1) && ((ret[0] < mn) || (mx < ret[0])) then return '-' else return ret.join(',') end end # main while line = gets line.strip! next if line.empty? mn, mx = line.split(',').map{|a| a.to_i} puts solve(mn, mx) end
★★★の問題です。かなり難しいと思います。
入力値を数値にし、それぞれをsolve()に渡し、結果を印字します。
定数Countsは各桁までに何個の回分数があるかの数列で、0桁目からになっているので先頭の値は0になっています。
問題を解きます。
64行目でmxを999,999,999,999,999に丸めています。入力値の最大は1,000,000,000,000,000ですがこれ以下の最大の回分数は999,999,999,999,999なので丸めてしまいます(手抜き)。
67〜68行目で下限以下で最大の回分数がその桁の何番目か、70〜71行目で上限以下で最大の回分数がその桁の何番目かを求めます。
74,75行目で下限と上限がそれぞれ全体の何番目かを求めます。
78行目は上限以下の最大の回分数が下限以下の最大の回分数より小さい場合、相当するものはないのでそのチェックです。
85〜99行目で中央値を求めます。
下限の値が回分数の場合、その場所を探索範囲に含めないと1子少なくなってしまうのでチェックし、個数を調整します。
下限以下と上限以下の回分数の個数が決まったらその平均が求める回分数の先頭からの番号になるのでそれを求めます。
102行目のretは答えを保持する配列です。
103〜107行目で答えを求めます。
108行目で先頭からの番号を何桁の何番目かに変換し、109行目でその場所の回分数を求めます。
109〜112行目で答えの印字ですが、ここでも不正値のチェックをしています。答えの小さい方がmn未満や大きい方がmxより大きい場合は答えとしておかしいので'-'を返し、それ以外なら','で連結した文字列を返します。
nが回分数かどうかをチェックします。
nを文字列にして両端から比較し、真ん中まで同じなら回分数、どこかで違えば回分数ではありません。
r桁のn番目(1始まり)の回分数を求めます。
1桁の場合はn-1になります。
2桁以上の場合は考え方に書いた通り、nを文字列としてnにnを反転した数を連結したものを求めます。
引数nは数値なので桁数が合うまで左右を0埋めします(右側だけ埋めれば良いがチェックが楽なので左右埋めています)。
得られた文字列に10r-1+1を加えると求める値になります。
引数n以下の最大の回分数がその桁の何番目か(1始まり)を求めます。
考え方に書いた通りの実装で、nの上半分とそれを反転した値を文字列として連結した値を作り、それがnより大きければnの上半分を1ずつ減らしながら求める値を探します。
引数idxは全体の何番目かを示す値で、それを該当する桁の何番目かに変換します。
Countsの要素と比較していって、当てはまる場所になったら一つ下の桁までの総数を引けば求める値になります。
桁数はsearch()を呼ぶ側でわかっているので返しません。
非常に難しい問題でした。
入力値からそれ以下の回分数が何番目かを求めて、下限と上限の真ん中の番号の回分数を求めれば良いというのはすぐに思いついてのですが、何番目の回分数かを求める、何番目という情報から該当する回分数を求める方法がわからず苦労しました("回分数 性質"で検索しましたが有用な情報を見つけられなかった)。
ブレークスルーは回分数を作る方法を考えていた時にn番目はnとnを反転した文字列を連結して10…01を足せば良いことに気づいたことで、これによって何番目の回分数かを求めるのも容易にできるようになったことでした(最初はもっとゴチャゴチャした方法でやっていた)。
この出題者の問題は例が詳しいので正答率が高い(挑戦者数とバッジ獲得数が近い)のですが、私が正解した時点で50%くらいしかない状態で、かなり難しい問題だったようです。