https://csacademy.com/contest/ioi-2016-training-round-1/summary/
横の長さがN、縦の長さがMの長方形で表される廊下があり、この廊下の左下の角を原点とする。この廊下にはK個の障害物があり、i番目の障害物の座標は(xi,yi)である。この廊下に左から入って右へ通過できるような円の半径の最大値を出力せよ。
すべての障害物と上下の壁を頂点とみなした重み付き完全グラフを考える。ここで、各辺の重みは障害物間のユークリッド距離(片方が壁の場合は壁と障害物もしくは壁と壁の距離)である。左から廊下に入った円が右に通り抜けることは、上述のグラフにおいて下の壁から上の壁へのあり得るpathすべてを横切ることであると言い換えることができる。あるpathを半径rの円が「通り抜けられない」条件は、そのpathを構成するエッジの重みがすべてr未満である場合であって、このようなpathが存在するとき、半径rの円は廊下を通り抜けることができず、逆にこのようなpathが一つもないときに限り廊下を通り抜けることが可能である(障害物の間を円が通り抜けられずに引っかかるとき、あり得るpathのうち必ずどれかに引っかかっている)。
重みがr未満の辺のみから構成されるpathがあるかどうかを調べることは容易であって、r未満の辺だけを使ってBFSを行い、下の壁に対応する頂点から上の壁に対応する頂点への到達可能性を判定すればよい。rを二分探索すれば時間計算量はO(K2logM)であるが、この問題ではK≤5000,M≤106と大きく、かつ制限時間が1000msしかないので間に合わない。
高速な方法として、最短経路の代わりに辺の重みの最大値の最小値をダイクストラ法で求めることによってこの問題をO(K2)で解くことができる。
解説AC。幾何だと思っていたらグラフ問題だった。自分での考察では全く手が出なかった。
N頂点の凸多角形がある。この多角形を、頂点間を線同士が交差しないように結ぶ(K−1本の線を引く)ことでK個に分割する方法の数を109+7で割った余りを出力せよ。
凸N角形の隣り合わない頂点どうしを線で結ぶと2つの凸多角形に分かれるが、これまでに引いた線と交差しないように次の線を引くことは新たにできた凸多角形の隣り合わない頂点どうしを線で結ぶことと言い換えることができて、これは元の問題のNおよびKが小さい場合になっている。ここで、DPテーブルを
DP[i][j]=凸i角形の頂点同士を線が交差しないように結び、j個の凸多角形に分けるときの線の引き方の順列の数
のようにとる。DPテーブルの更新順序はiの昇順であり、求める答えは順列ではないので、DP[N][K](K−1)!を出力すればよい。
凸i角形をj個に分割したいとして、最初に1本線を引く方法を考える。引いた線はi角形を2つの凸多角形に分け、このとき分けられた片方がk(≥3)角形であればもう片方はi−k+2(≤i−1)角形となる。ここで、凸i角形をk角形とi−k+2角形に分ける線の引き方はi通りだが、k=i−k+2である場合に限りi2通りしかない。
最終的にj個の領域を作りたいので、今できた2つの多角形をそれぞれ何個の領域に分割するかを振り分ける。片方をl個に分割するとき、もう片方はj−l個に分割することになる。それぞれの場合はより小さい問題の答えになっている。
以上のことから、DPテーブルの更新は以下のようになる。 DP[i][j]=i+22∑k=3j−1∑l=1DP[k][l]×DP[i−k+2][j−l]×(iori2)×{(l−1)+(j−l−1)}Cl−1 これは、上述のあり得る分け方をすべて足し合わせたものである。DP[k][l]とDP[i−k+2][j−l]をただ掛けただけでは順列の数にならないので、最後に二項係数を掛けている。
自力AC。解法自体はすぐに思いついたが、実装に少し手間取った。N≤125でO(N4)なので間に合うか多少不安だったが、蓋をあけてみればかなり余裕だった。なお問題の性質上、DPテーブルをそのまま埋め込めばO(1)も可能。
自然数Nに対応するFarey数列FNとは、区間(0,1)にあるすべての既約分数のなかで分母がN以下であるようなものを昇順に並べた数列であり、たとえば F5=15,14,13,25,12,35,23,34,45 となる。自然数N,Kが与えられるので、Nに対応するFarey数列FNのK番目を出力せよ。
XNとX+1Nの間にK番目の要素を含むようなXを見つけて、この間の区間のみFarey数列を実際に構成することでこの問題を解くことができる。
Farey数列FNのなかで分数XNが何番目の要素かということを調べる方法は以下である。DPテーブルを
DP[i]=FNのなかで、XN以前にある分母がiの要素の数
のようにとる。ある分数ABが約分できるならFarey数列の要素ではないので、DP[B]はAB≤XNであるようなABのなかでGCD(A,B)=1であるようなAの数と言い換えることができる。ある分数ABが約分できる場合、約分したあとの分数がBより小さい分母で既に現れているはずなので DP[B]=⌊XBN⌋−∑DDP[D] と表せる。ここで、DはBの約数である。このDPの更新はエラトステネスの篩の要領で行うことができて、DP[i]を決定したあとiの倍数すべてからDP[i]を引くことを繰り返せば全体でO(NlogN)となる。これをすべて足し合わせたものが求めるindexである。
以上の方法を使って二分探索を行えばXNとX+1Nの間にK番目の要素を含むようなXを見つけることができるので、次にこの区間内のFarey数列を実際に構築する。
Farey数列FNにおいて、分母がB(<N)の分数はXNとX+1Nの間に自明に高々1つしかない。分母Bについて、そのような分数の分子の候補AはXN≤ABとなるような最小の分子 A=⌈XBN⌉であり、AB≤(X+1)NかつGCD(A,B)=1の場合のみFNの要素となる。
以上のようにして実際にFNの一部が構成できたら、ソートしてK番目の要素を数えて出力すればよい。
解説AC。二分探索っぽい雰囲気は結構感じていたが、分数で二分探索するのが難しく諦めてしまった。