2019年9月2日月曜日

ABC 139 F - Engines

ABC 139 F - Engines

偏角ソートして走査

NN本のベクトルv1,...,vNv_1, ..., v_Nを任意に組み合わせて長さ最大のものを作る問題。偏角が[θ,θ+π][\theta, \theta+\pi]の区間に入っているベクトルは方向 θ+π/2\theta + \pi / 2への長さの増加に寄与するので、v1,...,vNv_1, ..., v_Nを偏角でソートしておいて、各区間[θ,θ+π][\theta, \theta + \pi]についてベクトルを加算して長さを比較すれば良い……のだけど、θ\thetaは連続量なのでどのθ\thetaを調べれば十分なのか考えなくてはならない。

以下、v1,...,vNv_1, ..., v_Nが既に偏角でソート済みとする。各viv_iの偏角を基点にして+π+ \piの範囲にあるベクトルを加算するという方針でだいたいは合っていそうだが、(0,10),(1,1),(1,1)(0, 10), (1, -1), (-1, -1)のようなケースでうまくいっておらず、(0,10)(0, 10)のみを使うパターンが漏れてしまう。180°180\degreeの区間の両端がどのベクトルにも一致しないような、各偏角と偏角の「間」も基点として使う必要がある。

これを実現するには、各viv_iの偏角に微小にプラスした地点から+π+ \piの範囲までの走査も追加すれば良さそう。上の例で言うなら、(1,1)(1, -1)の偏角に0.000000001°0.000000001\degree足したところから+180°+ 180 \degreeの範囲まで走査すれば、「間」のパターンを拾えている。ただ、実装にあたっては、いちいち微小な角度を足す必要はなく、viv_iを基点にして+π+ \piの範囲まで走査してvi+vi+1+...+vkv_i + v_{i+1} + ... + v_kというベクトルが得られたら、基点のベクトルを除いたvi+1+...+vkv_{i+1} + ... + v_kも調べれば良い。

また、これだと各viv_iから反時計回りの走査しかしていないので、時計周りにも調べる必要がある。各ベクトルのxx成分をすべてマイナスに反転して鏡映にした上で、まったく同じ処理を適用するのが簡単だと思う。1

計算量は、素朴に走査してO(N2){\mathcal O}(N^2)。尺取り法っぽくやれば走査の部分はおそらくO(N){\mathcal O}(N)にできて、その場合はソートが律速でO(NlogN){\mathcal O}(N \log N)


コンテスト中は漏れるパターンの拾い方を詰められなくて、走査範囲をπ/2\pi/2からπ\piまで雑に刻んで、通らなかったら乱択を加えるしかないと思っていたのだが、あっさり通ってしまった。偏角ソートしたら単に全区間をチェックすればよいということに気づけなかったのは反省。

凸包

原点のみの集合P0:={(0,0)}P_0 := \{(0, 0)\}からスタートして、与えられたベクトルv1,...,vNv_1, ..., v_Nによって移動可能な点を順に増やしていくとする:

Pi:=Pi1{p+vi:pPi1}P_i := P_{i-1} \cup \{ p + v_i : p \in P_{i-1}\}

PNP_Nの点の中で原点からの距離がもっとも大きいものを求めればよい。

問題はPiP_iのサイズが指数的に増えることだが、凸包に含まれる頂点しか答えに影響しないので、毎回PiP_iの凸包を取ればよい。凸多角形とその平行移動の凸包を取ると、高々22しか頂点が増えないので、PiP_iのサイズが爆発しなくて済む。凸包がO(NlogN){\mathcal O}(N \log N)で求まれば全体の計算量はO(N2logN){\mathcal O}(N^2 \log N)


  1. 既存のテストケースだと反時計回りだけでも通るらしい。しかし、時計周りだけでは通らなかった↩︎