ABC167

ここ1日くらい気分が沈んで(何かあったわけではなくなんとなく)のんびり過ごしていた。そういうときにありがちな、コンテスト中に心拍数がやたら上がって、解法の厳密性の確認にやたら時間をかけてしまう感じのやつになった(時間かかるのは普段からという気もするが)。でもEFをそれなりに早く解けて、久々に"天才"を見せてしまったかなと(これだよ欲しかったのは)。

A - Registration

TがSより1文字だけ長い制約であることを確認して、t.pop_back()。問題文を読むのにけっこう時間を取られてしまった。

B - Easy Linear Programming

B問題だしどうせ 1<=A,B,C<=100 とかだろと思って、長さA+B+Cのvectorを構築する解法を考えていたところ、なんと10^9。これは、A以下か、そうでないならA+B以下かで場合分けする。最近のB問題は甘えさせてくれないな。

C - Skill Up

最近のC問題むずくないすか。まあよく見ると実装が少し重いだけ。問題文を読むのにかなり時間かかったけど、どれを買うか全部試せばいい。

D - Teleporter

最近やったPAST2のEを思い出してしまうが、Aは順列と限らないことに注意。まずダブリングが浮かぶが、順列でないにしても高々N回でループすることには変わりないので、ループするまでシミュレーションする。そこまでにK回以上テレポートしていればシミュレーションの記録から答えがわかるし、Kがもっとでかいならループに入るまでの回数を引いてからループの長さで割ったあまりを計算し、それでループ内の何個目が答えかわかる。必ずしも町1に戻ってこないことに注意。ミスが怖くてガンガン変数を宣言していったが、もう少し空間計算量の定数倍を減らせるだろうなあと思いながら書いていた。

E - Colorful Blocks

O(N^2)のDPができそうだなあというのを最初に思う。それを考えたのちに高速化するという方針がある。まあF問題も一応目を通してから本格的に考える。まず、DPをするにしても、具体的に何色かというのは関係ない。隣同士が同じ色というのが何箇所あったかだけが重要だ。図を描いて考える。1個目と2個目が同じ色かで分岐(同じ色が1通り、違う色がM-1通り)、2個目と3個目が同じ色かで分岐。ここまで描いて、パスカルの三角形に見えた。まさに合流(同じ色の組がそれまでに何個あったかが同じとき合流)の仕方が同じだ。最終的に組の個数は0からN-1までのN通り。0からKまでコンビネーションを計算して、違う色がたくさんあるぶん(M-1)^(N-1-i)を掛ける。サンプルが合わなくて「えっ」と声が出たが、最初の1個は1通りではなくM通りだった。Kまでの和を求めて最後にMを掛ければよい。

F - Bracket Sequencing

括弧もPAST2のKでやったので経験が生きた。まず、括弧を開くのを+1、閉じるのを-1として、和が0でなければダメ。それに加えて、途中までの和が負にならなければOK。ということで、N個の文字列は、+1,-1に変換したときの和(これをdとする)と、途中までの和の最小値(これをmとする)だけに注目すればいいのではと気づく。そのように変換し、じゃあどのようにつなげればいいか。具体的に構成する解法かどうかもわからない中でしばらく考える。まずdが正でmも非負のやつは使ってしまっていい。mが負のやつはそうやって稼いだ貯金がないと使えないので。dが正でmが負のものは、やはり貯金を稼ぐために早く使いたい。ここはmの絶対値が小さいものから。dが正なので、後になるほど状況は改善する。dが0以下のものしか残ってないとなったら、もう状況は改善しないので、mの絶対値が大きいものから使う?それは違いそうだ。状況を激しく改悪するものを先に使ってはいけない。考えてみると、あるS_iを使った(使えた)ときd-mのぶんだけ余裕が残るわけで、最終的に可能であるならd-mがより小さいものを後から使えば問題なさそう。考え直すと自信がなくなってくるが、さすがに時間も時間だし実装に入る(ここまでも、考えを寝かせる時間として実装はできるところまで進めており、いい立ち回りだった)。難しいのはソート(ソートしたら実際に構成して確かめるだけ)。まずdが正かどうかで判定、それが同じならdが正かどうかで場合分けしてmかd-mで判定する。なんとかAC。よかったー。

PAST202004-OPEN

早めのバチャ。関連ツイートはけっこう見かけたけど、こういう抽象的なネタバレってどのくらい影響するんだろ。M以外を解いて94点。

A - エレベーター/Elevator

Aからクソムズ(まあABCとは違うので)。最初の文字が'B'かどうかで分岐して、1階が0となるように文字列を数値化する。あとは差の絶対値。

B - 多数決 / Plurality Voting

これは数えるだけなので簡単。今回は、配列を256まで用意して max_element(p, p + 256) - p をcharにキャストして出力した。

C - 山崩し/Landslide

読むのが大変だが、下から逆三角形に広げていくやつ。素直にシミュレーション。更新対象は'#'だけなので、横方向の範囲は1から98までと大雑把でいい。

D - パターンマッチ / String Match

えらく難しそうだが、かなり小さい制約なので全探索できる。英小文字に'.'を加えて27+27^2+27^3。計算しやすいように'.'ではなく'{'('z'+1)でやる。Sの探索範囲に気をつけて。Sの中で1箇所でもマッチしたら答えをインクリメントしてそのTの探索をやめる(ここけっこう混乱した)。

E - 順列 / Permutation

サンプルを見て何かサイクルに分解するやつかと思ったけどNが異様に小さいので素直にやるだけ。

(追記)必ず元の数に戻ってくる証明がわからなくて焦って、結局問題文を信じてそのまま提出してしまった。元の数を含まないループにはまるためには、そのループに含まれるある数への移動元が2つないといけないけど順列なので1つしかないね。

F - タスクの消化/Tasking

よくあるやつだけど難しいよね。まず、kの値によって1日目2日目あたりの戦略が変わるんじゃないかという心配が出る。ただ、実行できるタスクは増える一方なので大丈夫そう。実行解禁される順にソートして、日毎に実行できるものをpriority_queueにつめていって、貪欲にポイントの高いものをやっていく。

(解説PDFを見て)バケットソートでもいいのか。Bが小さいことを利用する方法もあるんだね。こういうの思いつきたいけど、結局実装としてはソート+優先度付きキューってことになりそう。

G - ストリング・クエリ/String Query

queueを使ってガシガシやる。削除して半端が出ないケースを慎重に。

H - 1-9 Grid

この制約ならダイクストラ法で問題ない。数字が1から9の9個で、ステージがいくつあるかがなかなかややこしかった。最初が0、1を踏んだら1、9を踏んだら9ということで10枚だ。0枚目のスタートから9枚目のゴールまでの最短距離がわかればいい。グリッド上の辺を列挙するのは何回かやって慣れてきたけど、向きによってどこの数字を見るか変わるしさすがに少し時間がかかった。

I - トーナメント / Elimination

難しそうだけど、単にトーナメントで何回戦うか答えるだけ。それもわりと難しいが、少し考えていい実装を思いついたので書き進める。vectorで人の番号と強さを持ち、p[i] = (p[i * 2]とp[i * 2 + 1]の勝者) とする。勝った側の勝数をインクリメントしていく。繰り返し毎に人数は半分になるので、計算量にlogは付かない。何回勝つかを求めて、優勝者以外に1を足してもいいのでそうした。

J - 文字列解析/Parse

(最終的にも)1000文字以下なので、1個ずつ括弧を外していけばいい。最後に見た'('の場所を記録しておき、')'が来たらそれとペアにする(処理したらループを抜けてまた最初からペアを探す)。ペアの間には'('も')'もないことが保証できる(ちょっと不安だったが、どちらもないんだからしょうがない)。substrとか使って雑に連結して間に合う。括弧がなくなったら出力。

ここまで、考察または実装が軽い問題も多く、かなりいいペースで解けている。

K - 括弧/Parentheses

10分くらい考えて何もわからんので次の問題へ。'('を+1、')'を-1として途中負にならず最終的に0ってことだよね。どうやってそれを実現するか。2種類の操作を別々に考えてくっつける?O(N^2)は間に合うし。でも削除だけでもわからん。難しすぎる。

Oまで通して、残ったKとMのうち簡単そうなKをやる。DPでできるやんけ。しかも2種類の操作は一緒にやるとしても同じことだ。DPに気づいた瞬間こんなに簡単だとは。ただし実装までイージーではない。INFで初期化すべきところをちゃんと初期化する。+1はN/2を超えてしまうと0に戻れなくなるので ll dp[3001][1501]; くらいでいいかと思ったけど、実装のしやすさから ll dp[3001][3001]; にした。N/2が整数とは限らんし。dp[i][n]をINFにして、あと特別扱いが必要なのはdp[i][0]だけ。そこは書く。最後はdp[n][0]を出力するだけなので楽。

L - 辞書順最小/Lexicographically Minimum

判定は簡単なのにわざわざ不可能な入力もありになっている。判定を書くことでスムーズに考察できるという誘導だろうか。最初の数は可能な最小のもの、最小となる数がたくさんあっても番号が若いものを取って損しない。次以降の数も同様なので、最小のものをどんどん取っていけばいい。セグ木にA_iとiのペアを乗せる。「A_iが同じならiが小さいものを選ぶ」という処理を書くのが面倒だったのでpair<int, int>にした(minをとるだけでいい)。(K-1)*D+1文字が必要で、それだけあれば数列は作れる。あとは区間をDずつ更新しながら出力していく。

問題が比較的軽量なのはここまでだった(前回と違って、最後の3問だけがはっきり重い)。

M - 食堂/Cafeteria

問題設定がヤバい。回数と時間がごちゃごちゃに混ざる。読むたびに誤解して趣旨を知ることができない。「前回の利用がL日前である場合のみ」のところ、L日前でないとき食べないのはわかるが、L日前なら必ず食べるとは言ってないように見えて混乱した。10分ちょっとして、ヤバいので次の問題へ。

他の問題を解き終わり、残った35分をつぎ込んで考える。制約が10^5なのでかなり頑張って計算量を落とさないといけない。まず、料理毎に(10^5個の)vectorを持ち、その料理が出される日を詰め込む。間に好みの料理以外を食べる回数を保存しておきたいのでpair<int, int>にする。使いやすいように、vectorの長さを1増やす(のちに2倍にした)。しっかり0-indexedに変換しておく。あとはクエリに答える(好みが被ってたり最初の利用日が違ったりヤバ杉)。答えるのは好みの料理を食べる回数だ。好みの料理でないものを食べた回数や料理を食べた回数ではない。まず二分探索で次に好みの料理が出る日を求める。そこまでに回数を使い切ったら0を出力。次に、1周期で何回食べるかで割り算してTを剰余で小さくしておく。最後に、二分探索であと何回好みの料理を食べれるか求める。好みのだけでいいので少しは楽。この最後の二分探索の細かいところを詰める時間がなくて、終了となった。

結局この方針でACできたが、時間はめちゃくちゃかかった。難しいというより、(バチャが終わってしまうと)考えたくないくらい複雑だ。なお想定はダブリングらしい。Twitterで見かけたダブリングってOじゃなかったんだね。

N - ビルの建設 / Building Construction

敷地は重なることもある。例えば3つ重なってたらその3つのコストの和を求めればいいわけだ。頼むから座標圧縮なしで解ける問題であってくれと祈りながら考えるが、(普通にやるならたぶん)必要だった。敷地とクエリを共通のstructに入れてx座標でソート。区間可算点取得をするためにBITを使う(いもす法のイメージ)。BITを使うために座標圧縮する。境界を含むので、x座標の方向ではソートのときに考慮し、y座標はBITの足す場所で考慮する。なぜかサンプルが通る(というくらい長い実装だった(途中で詰まったわけでもないのに))。方針は(典型なので)わりとすぐ見えたものの、やはり実装にかなりの時間をとられてしまった。

O - 可変全域木 / Variable Spanning Trees

なぜ頂点でなく辺なのだろうと思ったが、当然ながら全域木には全ての頂点が含まれるのだった。さて、まずは普通に最小全域木を求めてみる。そこに含まれる辺についてはこの全域木の重みが答え。問題はそれ以外だ。最初にその辺をつなげてからクラスカル法を適用するのとかを考えていたが、最小全域木に辺を付け足すというのでよかったんだ。木に辺を一つ追加すると閉路ができる。いわゆるなもりグラフ。その閉路から一つ辺を取り除いたものは木になるはず。つまり、閉路の中で重みが最大の辺がわかればいい!

ということでオイラーツアーとセグ木を貼ろうとする。グラフは、まず最小全域木を作ってその木で構築する。閉路は、元の木で考えるとLCAで合流する(面白い)。んで、オイラーツアーがあっても閉路の最大重みはわからないと気づく。マジかよ。LCAはわかるし、そこまでのパスもわかる、と思いきやオイラーツアーは寄り道するのでそのパスだけの最大値はわからない(普通にDFSとかででもできないか考えたけど何も出てこなかった)。困っていたが、突然ダブリングが降ってきた。LCAはダブリングで求めて、ついでに最大値もダブリングで求まるじゃないか!最小全域木の重みに今の辺の重みを足して、辺の両端の2つの頂点からLCAまでのパスに含まれる辺から最大の重みを求める。辺はコスト昇順でソートするので、辺番号が何の番号だか混乱した。

サンプルが合わない。ここで、最小全域木は一通りと限らないことに気づく。別の最小全域木なら、閉路にもっと違う辺が含まれていたのでは?これね、解法を証明してないというより、単にクラスカル法の証明を知らない(は?)。まあ、だとしてもこの単純なサンプルが通らないのはおかしいのでバグを探す。なんと、木を構築するときに、逆向きの辺を追加せず同じ向きの辺を2回追加していた。コピペしてそのままかよ。サンプルが合ったので提出して(雰囲気的にクラスカル法が正しいならこれも正しいやろみたいのはあった)AC。実装だけでなく、このデバッグに時間を大量消費させられたのは痛かった(もっとも、これだけ重めの問題を解いていたら1回くらいはこうなるか)。

ABC166

昨日とまるっきり違うセットだったらしく、天才が無限人いるように感じられてワロタ。特に、DEを早解きできる人がここまで多いのは意味がわからない。体感で昨日よりそんなに悪い出来とも思えないが、青コーダーにとっては全完早解きセットだったわけで、スピードの差が出たということだろう(もちろん実力が高ければスピード関係なくいい順位が取れるけど、さすがにスピードをカバーできるほどの実力があるとは思っていない)。

A - A?C

夢のような問題設定ですね。stringで受け取って、s[1] ^= 'R' ^ 'B';として出力。

B - Trick or Treat

入力を受け取れますかという問題だなあ。長さNの配列を用意し、入力を受け取りながらお菓子を持っているすぬけ君にチェックを入れて、最後に数えればいい。すぬけ君がたくさんいるのけっこう混乱する。

C - Peaks

少し考えたけど、まあグラフとして受け取る。一本の道と一本道は違う。最近、天才系ではなく全探索系のCが多いね。

D - I hate Factorization

見た目がヤバい。しかも、「出力は○○以下の整数」とか書いてない。条件を満たせばどんなに大きな整数でもいいということだが、つまりそういう解は存在しないということか?焦る。まあよく考えるとiの5乗とi+1の5乗はiの4乗のオーダーの差があるので、でかい数は何と差を取ってもでかい(解はXと比べてそんなに大きくない)とわかる。差がXより大きくなったら不必要なので、そこで打ち切って候補のリストを作る。1000個もないので2乗の全探索でOK。符号がけっこうややこしいので不安だった。

これ落ち着いて考えるとただの全探索なんだよね。候補が1000個以下だとさえわかれば、ほんとに全探索するだけ。符号を考える必要もない。

E - This Message Will Self-Destruct in 5s

これは難しい。身長とか距離とか色々固定して考えてみるがどうにもならない。ただ、ある人とペアになれる相手の身長は遠くなるごとに1, 2, 3,...と増えていくと決まっている。また、右側だけを見ることにすれば絶対値を考える必要がない。つまり、A_iをA_i - iに置き換えることで、あとは右側にA_i - i == hogeとなるiの個数がわかればいいことになったり。しそうだ。A_i - iは負にもなるので配列でやるのは混乱しそう。普段使わないが、平衡二分探索木を使おう。set::multisetでいいかな。TLE。「は!?」声が出た。

慌てて調べると、multisetのcountの計算量は個数にも依存する。はい。よく理解してないうえに慣れないことをするもんじゃないね。定数倍が重いからなるべく使いたくなくて経験値がないんだよな。TLEはほんとに悔しい。std::mapに直してAC。左側を見ればよかったかな。あまりにも難しくてstd::vectorで済ます余裕がなかった。

F - Three Variables Game

これも入力受け取るのめんどいじゃん。sは2, 1, 0に変換して保存した。A, B, Cもa[3]にしたけど結局それを生かした実装はできなかった。少なくともただの貪欲(小さくないほうを減らす)はダメ。2^N回の選択をするというのはいかにもDPだが、状態が多すぎて間に合わない。ここで、碁石を使って考える。碁石を1つ移動させる操作と考えられる。これさあ、A, B, Cは10^9以下だけどN以下としても同じだよね、そして10個くらいまで減らしてもよくね?実行速度(使用メモリ)と実装のしやすさを勘案し、A, B, Cは6以下に切り詰め合計18個までの碁石が存在する前提でDPした。DPテーブルは(10^5+1)*(18+1)*(18+1)。あとはDPするだけ。実装がクソ面倒だけど。その状態に遷移可能なら、そこから到達できる場所(最大で2か所)を1にする。減らすやつが0より大きいことだけチェックすればいいというのが、さっき言った実装のしやすさ(18個以下しかないので19個以上になるかチェックする必要がない)。あとはそれを逆に辿って選択方法を構成する。ここも面倒。面倒な書き方しかできないの力不足だなあ。最低限きれいに書けてACできたのも実力ではあるけど。6個より多い部分は使わなくても大丈夫というのを、(実装する時間まで考えると全然時間もないので)証明せずに使っていたのでそこが不安だった(まあ長々と書いたコードも不安ではあるけど別物だよね)。これ解説PDFの方針を本番で見つけるの"強さ"が必要だなあ。俺はDPに逃げてしまうよ(ところでこういう典型DPだいぶ慣れたね)。

ABC165

ノンペナ全完ヒャッホーーー!!いやあCDが瞬殺できなかったときはどうなるかと思ったが、FがシンプルでEも意味がわかれば面白かったのでよかった。

A - We Love Golf

何このクソムズA問題。for文使って書いたけど、A問題ってことはループを使わない想定なんだよね。まあ(A+K-1)/A*AとしてBと比較するみたいのは思いついてたけど難しいでしょ。今気づいたけど、B/K*KをAと比較したほうが簡単だ。

B - 1%

これもむずい。最初、t*101/100みたいのを考えてて、オーバーフローしそうだから困ってた。t+=t/100でいいんだね。X=100のケースがないのが親切。最大回数の見積もりもB問題にしては難しいなあと思っていたが、入力例2にX=10^18のケースがあることにACしてから気づいた(いやちゃんと数えてはなかったけどこの見た目で10^17とかだったら詐欺でしょ(いやそれは甘えかなあ))。

C - Many Requirements

ヤバC。まず列挙ができない。少し考えて、M個の1の間にN個の仕切りを入れるのだと気づいた。20C10をまず計算して(ググって)意外と小さい(10!より小さいと思わなかった(感覚がひどい))ので(時間をかければ)解けそうだと思った。考察を寝かせるためにD問題へ。

あとは頑張って実装。M個の1ではなく、M-1個の1だった。差だけが重要なので0以上M未満としてよい。next_permutationで全探索。1の個数を累積させ0に当たったらA[j++]という感じで整数列を構成。あとは言われた通りに総和を求めてそれの最大値。けっこう難しいし実装も重い。これを早解きするのは化け物。

D - Floor Function

B倍して式変形するとx%B*A/Bの最大値を求めればいいことになる(B倍したらちゃんとBの倍数になって不思議だった(不思議、つまり理解してないのに答えがわかるのが数式のいいところの一つ))。x=min(N, B-1)とすればよい。

E - Rotation Matching

問題文を読むのがかなり大変そうなのでF問題を先にやった。FをACしてからじっくり読んでみると、内容自体は別に複雑じゃないし面白そう。まず、条件を満たさない割り当てを頑張って見つける。例えば差が1になるような整数のペアが2つの対戦場に書いてあると、隣の人と2回対戦してしまう。なるほどそういうゲームか。そして、差が1と差がN-1は同じことだ。つまり、Nが奇数のときはサンプル2のようにすればいけそう(ずいぶん親切なサンプルだと思った)(差が1,3,5のようにして(裏が6,4,2なので)被らない)。Mが小さいときは楽になるだけなので途中まで出力するだけでいい。

偶数のときも似た感じでできないか考える。差が0とN/2はダメ。例えばN=10のときは差が1,3,6,8とすればまあよさそう?いくつかの例を慎重に考えて大丈夫そうなので(証明してないけど(おい))それで実装した。差がN/2以上になったら1個広げる感じで。こういう実装は苦手意識があったけど、思ったより短く書けてAC、全完。

F - LIS on Tree

例によってChokudai SpeedRun 001のH問題の自分の提出を見に行く。LISには、lower_boundを使う方法とBITを使う方法があるので、どっちが使えるだろうなあと思いながら木上のDFSに思いを馳せる。根から葉までのパスでそれぞれ普通にLISすればいいだけだが、それを高速にできるかという話。lower_boundのほうはなんか簡単に差分計算できそうに見える(ある場所の値を更新して必要ならお尻を伸ばすだけなので)。解けてしまった。正直言ってLISはすらすら書けるほど理解してないので、コードを組み込む感じでミスが入り込まないように書く。サンプルが通ってから最後の見直しで、x.assign(n, n);の右側のnが要素数ではなく要素の最大値だと気づき1e9+1(+1は念のため(もう脳が限界なので))に書き換えてgot kotonaki(見直しが奏功する珍しいケース)(追記:と思ったらイテレータで使う範囲を管理してたので値nは使ってなかった)。