ABC333

6完。Fが得意寄りだったらしくパフォはいいが、Gみたいのが全然届かないんじゃなあ。悔しいけど嬉しいという感じ。ワンペナでパフォ100変わるのは怖い。

A - Three Threes

string(n, '0' + n)

B - Pentagon

目視で確認すると、相手は4通り、距離は2通り。文字の差の絶対値が1,2,3,4のどれかになるのでmin(a, 5 - a)で折り返して(1,2,2,1にして)一致するかを出力する。

こういう処理(2回やる必要がある)を関数にするかは微妙なところ。今回は2回書いた。基本的に同じことを2回以上書かないべきだが、簡単な問題でスピードを出したいときはそうしないこともある。

C - Repunit Trio

全探索できそう。ただ、どこまで大きいレピュニットを使う必要があるかわからないのでまずそれを調べる(実はサンプルに書いてあった)。18番目のレピュニット(18桁なので10^18より小さい)まで使い、合計が10^10未満のものを3重ループでstd::setに入れていき、何個あるか数える。これで333個には足りなかった。10^12にすると足りた。10^12未満のものが333種類以上作れるということは、仮に抜けがあったとしても答えは10^12未満に収まるということだ(実際は、抜けはない)。ということは、13番目以降のレピュニットは使わないとわかる。よってそのまんま18番目までstd::setに入れる処理のままでN番目を出力すればいい(怖がって17番目までに直した)。

こういうのわりと好きだけど、時間は食ってしまった。

D - Erase Leaves

最初、頂点1が削除されるの常に最後じゃんと思ってしまったが、サンプルの図を見て間違いに気づく。そこが葉だという意識がなかった。次数を1以下にするには、子のうち1個を除いて全て削除しないといけない。Nは2以上なので子が1個もないことはない。よって、部分木のサイズを求めつつ、根では子の部分木のサイズの最大値を求める。最大の子だけ削除しなくていいので、それをNから引いたら答え(最初、葉になるまでの回数を求めて1ずれてたけど、削除するまでの回数だった)。

解説、UnionFindでできるんだねえ。

E - Takahashi Quest

後ろから。モンスターに対応する最も近いポーションを取得する。これで、持っているポーションの個数を(どの段階でも)最小にできる。後ろから見て借金をタイプ毎にカウント、ポーションを使用と拾得の個数を記録に付けておく。借金が残っていたら-1。そうでないとき、ポーションの持ってる個数をシミュレーションして最大値を求める。あと出力。実装が長くなってしまい苦手。

F - Bomb Game 2

Nが1小さい問題の答えがあればという気がしたので、1から順番に求めていく。2から3を求める例で考えた。1/2の確率で先頭が取り除かれ、そうでないときrotate。2通りしかないので、両方の確率を書き出す。人iが最後まで並んでいる確率をp_iとすると、(Nが1小さい問題の答えと)p_1がわかれば全部わかる。p_1の一次式で表せそうなので、1周させて一次方程式になる。ゼロ除算とかもなさそう。p_1がわかったら改めて全部求める。2で割る処理が頻繁に入るので、2の逆元を求めておく。あとはvectorをswapしながらN-1回まわす。一応、N=3000で確率の合計が1になるのを確認してから提出した。

G - Nearest Fraction

連分数展開する。最初doubleでやっていたが、long doubleに変更して、ようやく整数でやるんだと気づいた。入力で与えられるのは実数と言いつつ有理数のみなんだよなあ。時間あったしかなり頑張って実装したが、結局Nという縛りがあるから連分数でできるわけではない。連分数展開って大きい数は出てこないがちだけど、出そうと思えば普通に出せるからなあ。できない。

(12/20追記)昨日はGMPに時間を費やした。スピードが必要ない場面で楽に扱える多倍長整数がないか調べて、boostで使えるのは知っていたが、GMPAtCoderで使えるのは忘れていた。自分はMinGW-w64を使っている。かなり調べてMSYS2もインストールしたりして、結局、Package: mingw-w64-x86_64-gmp - MSYS2 PackagesのFileのところにあるリンクからダウンロードすればよかった。libフォルダは-Lオプションで指定して-lgmpxx -lgmpも付ける。gmpxx.hとかはACLと一緒に置いて、DLLは実行ファイルの隣に置く。これで使える。MinGW-w64のgccにおいてlongは4バイト、GMPでlong longは使えない、ということに注意(仕方ないのでmpz_class(to_string(a))のように変換した)(mpq_classで分数も扱えた)。

解説を読んでも解説放送を見ても全くわからず、有理数の探し方 - gdgdiaryを見てようやく希望が見えた(二分探索木というより、間に分子分母足したやつを生やして倍々にしていく)。希望が見えたのでGMPに行ってたのだが、ここからも果てしなく長かった。まず、この木を目的の値に向かって降りていくことはできる。同じ向きに進むときは足す相手が同じになるので二分探索とかができる。方向転換すると指数関数的に分母が大きくなるので計算時間は問題ない。まず入力の実数(有理数)を分数に変換。より近い分数(同じなら小さいの優先)を更新する関数を作っておく。二分探索を改造して指数探索を作り使う。全然合わないが、精度が高くなるのは方向転換の1個前だった。よくわかってないけどとにかく使って少しでも仲良くなる、そうしないと何も進まない。二分探索は、現在位置がターゲットより小さければ、初めて「ターゲット以上になるまたは次で分母がNを超える」場所を探す。提出してWAが6個。0/1や1/1が候補になっていなかったのでそれを加える。提出してWAが1個。どうしても原因がわからず、苦しんだ。

気分転換してから連分数展開を使う方針をやろうとした。色々あったが、kyopro_friendsさんの提出で理解が進み(進んでも何もわかってないが)その方針でACした。連分数展開しながらStern–Brocot treeを下りることができる。さっきの自分のコードは3つの値(現在の位置と、足して現在の値を作った2つ)を持っていたが、現在位置と足す相手の2つを持てばよかった。なんか連分数展開で出てきた数を使うと超えないところまで進める。そこからもう一歩同じ方向へは進むのだが、例えば1/1から0/1の方向へ進んで1/3まで行ったとすると、そこがゴールでないなら次は確かに1/4へ行くのだけど、0/1から1/3の方向へ1/4, 2/7と進んでいくととらえる。こうして左右に動いて最後は分母がN以下になる歩数。なぜ歩数がわかるのか理解してないが、AC。やっと!

さっきのがなぜWAになったか知りたいけど知ることができない。ACするまでの経験値によりその方針でもコードが少しずつ変更されている。それを提出したらAC!あとはサブミット二分探索するだけだ。慎重に考えてWAの原因となりそうなところだけを戻して調べ、結局分母が0になるケースがあるのが原因だった。一歩戻る処理してたからね。

できるかどうかわからないことをやり続けるのは苦しい。もっと実例を手で計算していれば景色を知ることができたよなあ。つい頭で考えてしまう。時間効率がかなり落ちるのでよくないんだけどなあ。