全国統一プログラミング王決定戦予選/NIKKEI Programming Contest 2019

Updated:

全国統一プログラミング王決定戦予選/NIKKEI Programming Contest 2019

ボーダー付近になってしまいました。日本人 208 位でした。

Source codes

Solutions

A - Subscribers

$(\max(0, a + b - n), \min(a, b))$ を出力する。

B - Touitsu

貪欲にやる。 ary = [] に文字列を放り込む。 $i = 0, \dots, N - 1$ に対し、 hash = {} を作成する。 $ary[j][i]$ の数を出力する。あとは ans += 3 - hash.values.max とする。

ポイント

実装にミスがないことが重要である。 3 つの項を比較するのは結構面倒であるから上にような解答をした。

C - Different Strokes

解法

$(A _i + B _i, A _i, B _i)$ を sort, reverse して、貪欲にシミュレーションすれば良い。

解法の正しさの証明

$(A _i, B _i)$ というカードではなく、 $(A _i + B _i, 0)$ というカードでこのゲームをやることを考える。ただしゲーム開始前の得点は $- \sum _{i \in N} B _i$ 点であるものとする。すると、このカードを取ることは、高橋君にとっては $A _i + B _i$ 点を取ることになるので、元のゲームでは $A _i$ 点を取ることに等しい。青木さんにとっては高橋君に $A _i + B _i$ 点を取らせないことに相当するので、最初のマイナスを考慮すると、元のゲームでのスコアに $- B _i$ 点を与えたことに等しいので、すなわち $B _i$ 点を青木さんが取ることに等しい。改変されたゲームでは $A _i + B _i$ が大きい方から順に取るのが両者にとって最適戦略である。

ポイント

直感で解法がわかったので実装してそれで通った。入出力例が自明なものばかりだったのでますます確信があった。

D - Restore the Tree

まず逆辺を張るのがポイントである。こうすることで隣接リストでグラフを管理した際に empty であるものが root であることがわかる。

木に、子孫に向かって辺を張ったということであるが、この辺は要するに子を飛び越えているわけだから、そこから「逆算される仮の深さ」は必ず「真の深さ」よりも小さくなる。

そこで root の高さを $0$ とする。再帰関数で深さを求める。逆辺を張っているので、深さをそれぞれ求める。そこで最大の深さになったのが真の親である。こうして正しい深さが決定される。これを繰り返す。

ポイント

こうやって書くととても簡単な問題なんだけど、「辺を削除する」に頭が行き過ぎて、正しい解答になかなか至らなかった。

E - Weights on Vertices and Edges

基本的な着想:貪欲法 $O(NM)$ (TLE)

まず $(Y _ i, A _ i, B _ i)$ を降順に検討していく。この中で $A _ i, B _ i$ を含む連結成分の頂点のコストの和を求める。この和が $Y _ i$ 以下であれば、この辺を採用することができる。そうでなければ削除する。これを繰り返す。これが貪欲法に依る解法である。

高速化

この解法の高速化を試みれば良いわけだが、着眼点は大きく次の 2 つである。

  1. 「この中で $A _ i, B _ i$ を含む連結成分の頂点のコストの和を求める」のうち辺として採用しているものは、コスト $Y _ i$ 以下の辺である。つまり $Y _ i$ の昇順にグラフを連結していけば Union-Find で高速に頂点のコストの和を求めることができる
  2. 「この和が $Y _ i$ 以下であれば、この辺を採用することができる」の部分で、採用できる辺は、実はこの連結成分に含まれる全ての辺である。つまり $Y _ i$ の降順に DFS していけば $O(N)$ で無駄なくチェックできる

実装 $O(N \log N)$

まず typedef tuple<ll, int, int, bool> edge; を定義し、 vector<edge> V; に昇順で突っ込んでおく。次に vector<int> W[100010]; で連結リストを作る。ただし今回は $V$ の添字を入力して辺そのものへのリンクとしておく。 2. のために typedef tuple<ll, int> task; を用意しておき、 vector<task> T; とする。

まず 1. を実行する。 Union-Find の merge tech 版を用いて、初期値を $-X _ i$ にしておく。 $V$ から edge を取り出して繋いでいく。辺のコストが、連結成分の頂点のコストの和以下であれば、あとで DFS をするべきである。ゆえに $T$ に突っ込んでおく。

次に 2. を実行する。 reverse(T.begin(), T.end()); とする。 $T$ から出して DFS をする。使う辺ならば get<3>(V[x]) = true; とする。こうすると後ろの低コストの辺を前の方の高コストの辺でカバーしていることになり、無駄がない。そして最後に $V$ の第 $3$ 成分が false であるものの個数が答えである。

計算量は、 edge のソートに $O(M \log M)$ 、今回の Union-Find はならし $O(N \log N)$ である。 DFS は $O(N + M)$ である。ゆえに計算量は $O((N \log N)$ である。

ポイント

わかってしまえば簡単なんだけど、昇順と降順のいいとこ取りをするところが結構難しいと思った。

F - Jewels

ポイント

Others