2020年11月29日日曜日

CS Academy Round #76 - Pyramids

CS Academy Round #76 - Pyramids

ある種の区分線形関数に囲まれた領域の格子点を数える問題。区分線形関数を構成する線分の総数がO(N){\mathcal O}(N)なので、全部列挙して線分毎に下にある格子点を数えればよさそう。

まずは、与えられた点で折れ線を構成するものだけを左から列挙したい。点をccの昇順でソートしてスタックを持ちながら走査する。ある点(r,c)(r, c)による山を処理するとき、スタックの先頭に入っている点(=ひとつ前の山の頂上)による山との関係には次の3通りがある:

  1. 前の山に完全に含まれる
  2. 前の山を完全に含む
  3. 前の山と一部重なるか、遠く離れていて重ならない

今見ている点を(r,c)(r, c)、スタックの先頭に入っている点を(r,c)(r', c')として、

  • c+rc+rc+r \le c' + r'なら1、つまり前の山に含まれるので無視
  • crcrc-r \le c'-r'なら2、つまり前の山を含むのでスタックをポップしてさらに前の点を見る
  • それ以外は3なので新しい点をスタックにプッシュする

こうするとスタック(の反転)には山の頂点として有効な点だけが左から並ぶので、その間にできる谷に関する格子点をそれぞれ数えればよい。

数え上げパートについて。山の頂点が左から(r1,c1),...,(rk,ck)(r_1, c_1), ..., (r_k, c_k)と並んでいるとする。頂点(ri,ci)(r_i, c_i)(ri+1,ci+1)(r_{i+1}, c_{i+1})の間にできる谷は直線y=(xci)+riy=-(x-c_i)+r_iと直線y=xci+1+ri+1y=x-c_{i+1}+r_{i+1}の交点であり、そのyy座標は(ri+ri+1+cici+1)/2(r_i+r_{i+1}+c_i-c_{i+1})/2と表せる。そこで、H=max(0,(ri+ri+1+cici+1)/2)H= \max(0, \lceil (r_i+r_{i+1}+c_i-c_{i+1})/2 \rceil)としてまずは下部の長方形の格子点をH(ci+1ci+1)H(c_{i+1}-c_i+1)と数え、さらに上部の三角形の格子点を(riH)2+(ri+1H)2(r_i-H)^2 + (r_{i+1}-H)^2と数える。また、左端の頂点(r1,c1)(r_1, c_1)の山の左側半分にある格子点r12r_1^2個と右端の頂点(rk,ck)(r_k, c_k)の山の右側半分にある格子点rk2r_k^2個は別に加算する。このとき、各頂点の下にある格子点をちょうど2回ずつ数えているので、iri\sum_i r_iを引けば正しい数になる。