Atcoder: Nikkei Programming Contest 2019

published: ,
last modified: .

lang: ja

category: competitive_programming

tags: atcoder ocaml

A. Subscribers

難しい. 解答例

m=XYm=|X \cap Y|の最大値および最小値を求める問題.最大値がmin{X,Y}\min \{|X|,|Y|\}なのは分かる.最小値には「どちらも購読していない人たちが00人以上でなければならない」という条件が入る:XY=X+YXY=a+bm|X \cup Y|=|X|+|Y|-|X \cap Y|=a+b-mより,先の人数はn(a+bm)0n-(a+b-m) \ge 0,移項してma+bnm \ge a + b - nが必要.加えてm0m \ge 0であるから,連立させてmmax{(a+bn),0}m \ge \max \{(a+b-n), 0\}となる.

B. Touitsu

最初なんとなくどれかに揃えれば良さそうな気がしたがそんなことは無かった.a,b,ca,b,cii文字目について多数決を採ればよい.どれも異なればコストは+2, 1ペアだけあれば+1, すべて揃っていれば+0. 解答例

C. Diffrent Strokes

解答例

高橋くんが目標を実現するための手段としては「自分のスコアを大きくする」以外に「青木さんのスコアを小さくする」もある.ここから考えると,高橋くんがa[i]a[i]番目を取ると青木さんはb[i]b[i]を取れないから高橋くんはa[i]+b[i]a[i]+b[i]だけ得すると考えられる.よってa[i]+b[i]a[i]+b[i]をソートし,これが大きい順にa[i]a[i]を取ればよい.青木さん視点でも同様に考えればb[i]+a[i]b[i]+a[i]の大きい順に取りたいこととなる.したがって先のソート結果から二人して大きい順に取って行き,高橋くんの手番ではスコアに+a[i]a[i]を,青木さんの手番では-b[i]b[i]していけばよい.

editorial

解説では片方の得点だけを考えてよいようにゲームを変形している.青木さんがすべて食べた状態,すなわち高橋くんのスコアがb[i]-\sum b[i]の状態から高橋くんがiiを選んで自分のスコアにa[i]+b[i]a[i]+b[i]加算する(青木さんがiiを選んでもスコアが変動しない)ゲームを考えても同じこと.すると両者が注目すべきは高橋くんのスコアだけになる:高橋くんは自分のスコアを増大させることだけに専念すればよいのでa[i]+b[i]a[i]+b[i]の大きいほうから取って行けばよい.青木さんが出来ることは高橋くんが高いスコアを取れないように妨害する,すなわちa[i]+b[i]a[i]+b[i]の大きいほうから取っていくことだけである.

D. Restore the Tree

トポロジカルソートはこの前やったこともあり使いそうだとは思ったもののどう使えばいいかわからず撤退.閉路ができるのではと疑ってしまったのだが全くそんなことは無かった(木構造に対して親から子孫に矢印を引くので子孫から親方向の辺が作られることはない).

解説(editorial?)

解答例

根からトポロジカルソートすると(根は入力辺が無い点を調べればすぐに求まる),その結果はすべての辺のin/out順に並んでいる.いま要素vvに入る辺がp,qp,q(出現順序もこの通りとする)から出ているとき,元々の木に存在した辺はqvq \to v

証明(editorialが分からなかったので独自解釈,したがって誤りがありそう):pvp \rightarrow vが元々の辺だったとすると,操作によってqvq \rightarrow vを引く条件から元々の木にもqqからvvに至る経路qs1...sk1skv (k1)q \to s_1 \to ... \to s_{k-1} \to s_k \to v\ (k \ge 1)[1]が存在することとなる. まず元々は木構造であるから合流地点は存在しない:いまvvに入る辺はpvp \to vskvs_k \to vがあり,これらが同一でなければならないからsk=ps_k=pである.ここでqqからvvへの経路はqs1...sk1pvq \to s_1 \to ... \to s_{k-1} \to p \to vとなるが,これに含まれる各頂点はこの順序でトポロジカルソートに現れるはずである.これはp,qp,qの順序に矛盾.

一方でqvq \rightarrow vが元々の辺であるとすればp...qvp \to ... \to q \to vとなり,問題なく操作によってpvp \rightarrow vを引くことが可能.辺の出自がp1,p2,...,pl,qp_1,p_2,...,p_l,qと3頂点以上の場合も各pip_iqqに対して同様の議論を適用すればqvq \rightarrow v以外あり得ないことが分かりそう.

最初トポロジカルソートにおいて右側の要素の深さは左の要素と同等またはそれ以上だと勝手に思っていたのだがそんなことはない(wikipediaの図からも分かる).木構造でも片方の子を深く掘れば反例が作れる.

追記:editorial式

上を踏まえてeditorialを読むとそれなりに分かった気になれた:点vvに入る辺を{xiv}\{x_i \to v\}xix_iはトポロジカルソートに従って整列されているとする)とすると,合流地点が存在しないことから元の木でのxix_iからvvへの経路はすべてただひとつの辺pvvp_v \to vに合流する(pvp_v \coloneqq元の木でのvvの親).よってxix_ipvp_vの祖先(またはそれ自身)であるから,x1,...,xk,pv,vx_1, ..., x_k, p_v, vはトポロジカルソートにおいてこの順に現れることが分かる(重複部については順不同とする).いま{xi}\{x_i\}の中にpvp_vが存在するが,それはxkx_kしかあり得ない.

こうして見ると合流地点の非存在性を意識できなかったことが敗因な気がする.

別解

本番で思いついたものの詰めきれずに捨てた解法だったがこれでも正解できるようだ.各頂点について根からの距離が最長となるような経路が元の木の経路となる.理由としては操作によって追加される辺は先祖から子孫を繋ぐ,すなわち根からの距離を減少させるようにしか張れないことが挙げられそう.これにより距離最大以外の経路を採用すると最大長の経路が作れなくなって矛盾する(と思う).

今回のようなDAGの最長経路の求め方を検索すると,トポロジカルソートを利用したものが出てくる.GeeksforGeeksによると,トポロジカルソート順に頂点を調べ,そこを始点とする辺の終点について距離を更新していけばよいとのこと.理由が書かれていないので推測:通り過ぎた頂点に対して新たに最長経路が見つかるとそこから到達する頂点すべてに対して再計算が必要になるが,トポロジカルソート順だと一度通り過ぎた点はもう使われないために再計算が起こらないから? 解答例

ところで提出を眺めていると上位陣にこれをBFSで求めている解答が複数存在した[2]. BFSのアルゴリズムを思い出すと,BFS順で最後に点vvに到達するときが根からvvまでの最長経路となる(最短経路から順に見つかっていくので).したがってBFS順で最後になるまでは無視し,最後になったらようやく遷移をすればよい.最後かどうかはvvに到達する辺の個数を数えておけば判断可能で,このときに親ノードを記録し,遷移をしていく.重みが一定の場合にしか使えなそうだが考え方として面白かった. 解答例

最初は「根からDFSをして各頂点に対して根からの最長距離を記録しておけば,もう一度DFSをすることでその情報から経路を復元できる」という方針を立てていたのだがこの方針だと例の再計算を何回も行うことになりTLE.


  1. 後にも述べるが木は合流地点を持たないためpvp \to vqvq \to vが同時に辺であることはない.従ってs1s_1は必ず存在する. ↩︎

  2. https://atcoder.jp/contests/nikkei2019-qual/submissions/4100659 など. ↩︎