私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「ドット絵の丸が2つ」(CodeIQ)を参照してください。
【概要】
マス目に沿った、円のような形を2つ指定します。
両者に含まれるマスの数を計算してください。
【入出力】
入力は
1,2,3 4,5,4
のようになっています。
空白区切りで2つの図形が並んでいます。
各図形は、コンマ区切りで 中心のx座標、中心のy座標、半径 をならべたものです。
マス目の中心と中心の距離が、半径以下のマスが図形に含まれます。
具体的には下表のとおりです:
図形を表す記号 図形 0,0,0 1,2,3 4,5,6
出力は、普通に10進数です。
2つの図形の両方に含まれるマスの数を答えてください。
1,2,3 4,5,4
に対応する出力は、下図の通り、
10になります。
【例】
入力 出力 図 1,2,3 4,5,4 10 0,1,5 4,8,6 16 0,1,6 3,2,3 28
【補足】
不正な入力に対処する必要はありません。
中心座標は 0以上一万以下です。
半径の値は 0以上一万以下です。
中心座標・半径は、いずれも0以上の整数ですが、マス目はマイナスの座標にもあります。
Rubyで解答しています。
#!/usr/bin/ruby def points(x, y, r) ret = {y => [x-r, x+r]} for i in 1..r q = Math.sqrt(r**2 - i**2).floor ret[y+i] = [x-q, x+q] ret[y-i] = [x-q, x+q] end return ret end def count(a, b) return 0 if a[1] < b[0] return 0 if a[0] > b[1] l = (a[0] >= b[0] ? a[0] : b[0]) r = (a[1] <= b[1] ? a[1] : b[1]) return r-l+1 end def solve(a, b) pas = points(a[0], a[1], a[2]) pbs = points(b[0], b[1], b[2]) ret = 0 for y in (a[1]-a[2])..(a[1]+a[2]) next if pbs[y] == nil ret += count(pas[y], pbs[y]) end return ret end # main while line = gets line.strip! next if line.empty? a, b = line.split.map{|l| l.split(',').map{|v| v.to_i}} if a[2] <= b[2] then p solve(a,b) else p solve(b,a) end end
結構難しそうですが、以外にそうでもありません。
入力値を数値にし、それぞれをsolve()に渡し、結果を印字します。
小さい方の円を基準にするため半径を比較して小さい方をsolve()の第一引数、大きい方を第二引数に指定します。
aは小さい方の円、bは大きい方の円の[中心x座標, 中心y座標, 半径]です。
それぞれの円についてpoints()で{y座標 => [左のx座標, 右のx座標]}の連想配列を求めます。
小さい方の円のy座標の最小値から最大値までループし、重なり部分をcount()で求め、結果を加算します。30行目は小さい方の円のy座標に大きい方の円が無い場合なので無視します。
最終的にretが答えなのでそれを返却します。
考え方に書いた通り、円のy座標の中心から上下のy座標まで、各y座標の左右のx座標を求めます。
y座標が中心にあるときはx座標はx+rとx-rなのでretの初期値とします。
qは考え方に書いた通りfloor(√(r2-i2))です。
これを下側(y+i)と上側(y-i)について[x+q, x-q]とすれば左右のx座標が計算できます。
aとbはそれぞれあるy座標における、小さい方の円の左右のx座標と大きい方の円の左右のx座標です。
16〜17行目は円が重なっていない場合なので重なり部分0で戻ります。
19〜20行目で重なり部分の左側と右側のx座標を求めます。
l-r+1があるy座標の重なり部分なのでそれを返却します。
問題を読んだ時、高速化のためにはビル・アトキンソンがQuickDrawでやった方法を使う必要があるのかな、と思って調べたら結構工夫しないと単純に浮動小数点で計算した方が早いらしかったので、やってみたら十分間に合いました。
高速化に特に工夫がいらないのであれば単純な問題だと思います。