Panasonic Programming Contest 2023(AtCoder Beginner Contest 301)G問題メモ
Panasonic Programming Contest 2023(AtCoder Beginner Contest 301)
EとF、GとExの得点が微妙に異なるようになった。
G - Worst Picture
問題
- 3次元座標上に N 人、i 番目の人は (Xi,Yi,Zi) にいる
- 全ての人は、x 座標で正の位置にいる(Xi>0)
- x<0 の好きな位置 p から写真を撮る
- p,(Xi,Yi,Zi),(Xj,Yj,Zj) がこの順に一直線に並ぶとき、かつその時に限り、人 j は人 i に隠れて見えなくなる
- 最も写真に写る人数が少ない(隠れる人が多い)場合の、写る人数の最小値を求めよ
- 1≤N≤50
- 1≤Xi≤1000
- |Yi|,|Zi|≤1000
- 実行時間制限 4sec.
解法
3次元座標における直線処理と、有理数の正確な一致判定。
3次元座標に慣れていないと時間がかかるだけであって、やることはさほど複雑ではない。
(コンテスト本番では、100分しかない上に、他にも実装が重たい問題があったので、解答者がめちゃ少なかったけど)
Pythonなら、有理数を分数として正確に扱うには、fractionsモジュールが使える。
通常のintやfloatと比較するとそこそこ重たくはなる。
大まかな方針
まず、Xi が全て同じ場合は隠れるのが不可能なので N。それ以外を考える。
- 全2点間を通る直線を列挙する
- p はこれらの直線上のどこかになる
- 3人以上が同じ直線上にいるかもしれないので、直線は同じものは同じと判定できるようにし、直線毎に隠れる人数を数える
- 列挙した直線から、全2直線間の交点を調べる
- x<0 で交わる場合、そこを p にすればその2直線で隠れる人数を同時に隠せる
- この場合も、3直線以上が交わる点があるかもしれないので、交点が同じものは同じと判定できるようにし、交点毎に隠れる人数を数える
- 「1直線で隠せる人数の最大」または「2直線以上の交点から隠せる人数の最大」が、全体的な隠せる人数の最大
- N から引いたものが答え
直線列挙が O(N2)、交点列挙が更にその2乗の O(N4) となる。
同じ直線や交点を同じと管理するために連想配列を用いた場合、言語や実装によって logN が付く。
最大値の代入で約 3.5×107 となり、 有理数で計算を行う場合は定数倍も軽いとは言いがたいが、 実行時間制限を長めに取ってくれているので間に合う。
直線列挙
3次元座標上の直線は、3次元ベクトルとパラメータ t を使って →a+t→b で表すことができる。
→a が直線上の任意の1点、→b が方向ベクトルを示すので、2点を (X1,Y1,Z1),(X2,Y2,Z2) として、
- →a=(X1,Y1,Z1)
- →b=(X2−X1,Y2−Y1,Z2−Z1)
となる。ただ、これだと同じ直線でも異なる表現が可能になってしまうので、一意に揃える。
今回の場合、
- →a=(x,y,z) の x を 0 に揃える(直線が x=0 の yz 平面を通過する時の y,z 座標)
- この問題では x=0 の面を通過する直線のみを考えればよいことから、必ずこのような表現ができる
- →b=(dx,dy,dz) の各数値は、dx=1 に揃える
- これも、dx≠0 の直線だけを考慮しているので可能。一般の場合は場合分けが必要
ことで、一意にできる。(y,z,dy,dz) の4変数で直線を表現できることになる。
全2点間でその2点を通る直線をカウントしていくと、
k 個の点を通る直線は、k(k−1)2 回カウントされることになる。
カウントを2倍して平方根を取ることで、その直線で隠れる人数が特定できる。
交点列挙
(0,y1,z1)+s(1,dy1,dz1)=(0,y2,z2)+t(1,dy2,dz2) となる点の有無と、ある場合はその座標を求めたい。
x 座標の一致を考えると、パラメータは一致しなければならない。 s=t
y 座標を使って t を解くと t=y1−y2dy2−dy1 となる。
これが z 座標の z1+t×dz1=z2+t×dz2 でも成立すれば交点が存在する。
- ただし例外処理として、
- dy1=dy2 の場合、y1=y2 でなければならない
- そうでなければ xy 平面に投影したときに切片の異なる平行な2直線となり、交点を持たない
- y1=y2 の場合、y 座標は t の値に依らず常に一致するので特定できない
- →z 座標を使って t=z1−z2dz2−dz1
- これも、dz1=dz2 の場合、交点を持たない(z1=z2 なら、2直線は完全に一致してしまうのであり得ない)
交点の x 座標が x<0 となる時のみ、そこを p とすることが可能になる。
そのような交点ごとに、「どの直線で交わったか」をsetなどで記録しておくと、最終的にその交点でいくつの点が隠れるのか算出できる。
有理数の分子・分母の値の範囲
有理数で表現すると正確ではあるが、演算を重ねる内に分子・分母が巨大になったら、
結局、計算に時間がかかりすぎて扱えないという話になってしまう。
足し算ひとつとっても、分母が互いに素だと通分するだけでオーダーが2乗になるなど、結構あっさり大きくなるので侮れない。
コンテスト中は、「なんか意味ありげに Xi,Yi,Zi の制約がそこそこ小さめだし、配慮してくれてるんだろう」で投げてしまうことも多いが、本来はちゃんと考える必要がある。
各変数や結果の分子分母のオーダーを考えると、2次元で考えるとして、制約を |Xi|,|Yi|≤D として
- 直線を表す変数の dy(dx=1 に揃えた後)
- 2点を (X1,Y1),(X2,Y2) として、dy=Y2−Y1X2−X1
- 分子: O(D)
- 分母: O(D)
- 直線を表す変数の y(x=0 に揃えた後)
- y=Y1−X1×dy
- 分子: O(D2)
- 分母: O(D)
- ※y と dy の分母は一致する
- 交点を求める際のパラメータ t
- t=y1−y2dy2−dy1
- y と dy の分母の一致を生かして、分子分母に (y1の分母)(y2の分母) をかける
- 分子: O(D3)
- 分母: O(D2)
- 交点座標 cy
- cy=y1+t×dy1
- 分子: O(D4)
- 分母: O(D3)
となり、制約の4乗レベルまで値が大きくなる可能性がある。
今回は D=103 なので大丈夫だが、105 になるともう通常の64bit整数では不可能となる。