ABC247

ABCDEGの6完。昨日でARCもぷよぷよ最強リーグ観戦も終わったので今日は気楽。

A - Move Right

処理内容は単純だが問題文の読解が面倒。右シフト。先頭に'0'を入れてpop_back。

(追記)std::bitsetで入力もシフトも出力もできた(入力できるのは知らなかった)。

B - Unique Nicknames

普通に流し読みすると「あだ名はユニークでなければならない」に見えるけど、それよりも強い条件なので簡単になっている。ただ、イメージしづらい条件なので、心を殺して書いてある通りに「やるだけ」をする。自分の姓と名のそれぞれについて、他の人の姓名と一つでも一致するか調べて、姓と名どちらかでも一つも一致しないものがあればOK。全員についてOKならYes。見た目以上に階層が深く、その点でも難しい。

C - 1 2 1 3 1 2 1

まあ愚直に再帰しようと思ったが、vectorだと連結が楽な書き方でできるかわからず、stringにした。非再帰でできないかなとも思ったけどコンテスト中はさすがに再帰でいい。めっちゃ解かれてて、この見た目でこんなに解きやすい出題ができるのすごいと思った。

(追記)普通にループで構築できるね。s = "\1" からスタートして、s = s + (char)i + s を繰り返す。

D - Cylinder

まんまキュー。シミュレーションする。半端はキューの出口の個数をいじる。キューに入る情報の個数より多く出ることはないので計算量も大丈夫。

(追記)P &p = q.front(); みたいに構造体作って参照使えば可読性を上げることができた。競プロでも3分後の未来のために投資は必要だ。

E - Max Min

かなり難しく、FGを読んで考えてわからないこれはEをやらないとまずいと退路を断たれて考えるとようやくわかった。

まず、Xより大きいまたはYより小さい要素は使えないのでそれらで列を区切り問題を分割する。その中で、XとYを両方含む区間の個数を数えればいい。ここまでは簡単だが、ここからが難しい。使うXのうち最も左にあるものを固定して考えたりしても、左右にぐにゃぐにゃ伸びるのでよくわからない。

しゃくとりができそうだ。両方含むほうではなく、「両方は含まない」区間の個数を数えたほうがいい気がする。これもよくわかんなくて迷うところだがまあ解けそうなほうへ行くことになる。左端を固定し、XとYを両方は含まないままどこまで伸ばせるかを調べる。左端を1個右へ動かし、それによって状況は悪くならない(つまり現在の区間より右端が左にあるものは全部OK)ので、またどこまで伸ばせるか。しゃくとりできている。区間に含まれるXとYそれぞれの個数を管理しておけばいい。

(解説を見て)ぐえー包除思いつかねえ。ただ、どちらの解法にしても実装がけっこう苦手なやつだった(少なくとももう少し楽な書き方はあった)。

(追記)ユーザ解説を見てよくわかんなかったけど自分で考えたら、空間計算量O(1)でできると気づいた(通してから見直すと、ユーザ解説と同じことをやっていた(アイデアは受け取れていた))。右端の位置を左から順に試す。最後にXより大きいかYより小さいものがあった場所(なければ先頭の一つ左)の一つ右から、最後にあったXと最後にあったYの遠いほうの場所までが左端としてありえるので、その幅を足していけば答え。

F - Cards

DPとか包除とか考えても計算量がダメ。EGを通し残った20分で考える。サイクルか。サイクルに分解すると、独立に考えることができる。いつもながら、ごちゃごちゃしたトレードオフが、順列であるというだけでここまできれいになるの直感でわからない。あとはサイクル毎に(サイクルの長さをKとして)どこを空けるか(今考えると意味不明)K通りを掛けていくだけ。サンプル合わない。K個全部を選んでもいいからK+1通りか。これでサンプルが合ったので提出。よし7完だ、と思ったら盛大にWA。終了直後に、サイクルを辺で覆う通り数を考えないといけないことに気づく。0からK-1までの頂点。0から1への辺を置いたかと、直前の辺を置いたかでDPしていく。10分ちょっとかかって自力ACはできた。DPパートかなりきつい(普通に苦手)。

(追記)解説のDPなるほどなあ。ループなのが厄介だと思ってたけど、場合分けして切り開くことができるんだ。他の問題は復習したけど、これは実装する気起きないね(やはり苦手か)(得意なものを練習したほうが幸せになれるよ)。

G - Dream Team

最初に考えたときは何もわからなかったが、見た目がどう見てもフローなので無理やりグラフを作ってみると、なんと解けてしまった。最小費用流。被っちゃいけないのが所属大学と得意分野の2つなので入りと出の2つでちょうど間に合う。大学から人へ、人から得意分野へ、容量を1にして張れば、例えば同じ大学からは1人にしか流れない。コストだが、求めるものは人数を固定したうえでの最大値なので、強さを10^9から引いたものを使えばいい(人数*10^9から最小コストを引けば強さの合計の最大値になる)。あとはグラフがそこそこ大きいのでmin_cost_slopeを使う。処理としては簡単なはずの補間でけっこう時間を使った。確認のために補間が必要になる簡単なテストケースを手元で作った。

(解説を見て)あー、人に対応する頂点は必要ない。人に対応する辺を大学から得意分野へ張ればいいのか。大学から人への辺だけコストを設定して人から得意分野へのコストは0固定だったのでやや不自然だとは思ったけど。解説ではslope使ってなくて草。えー間に合うのそれ。slope使っても103msだったけど(あ、Nが小さいと思ってIO高速化してないから入力で多少かかってるか)。

(追記)人に対応する頂点を使わないようにしたら40msになった。補間は、auto v = g.slope(S, T); として (v[i].second - v[i - 1].second) / (v[i].first - v[i - 1].first) を足していけばいい(これは割り切れるはず)。

(追記)解説の計算量をよく見ると、言及がないだけでslope使ってた。