2021年3月31日水曜日

AtCoder Regular Contest 116

  ABDの三完。
 Cが解けなかったのが響いて青に落ちてしまいました。最近レートは上がっていないとはいえ、そろそろ黄色は安定したかな……と思っていたのでショックが大きい。


C - Multiple Sequences

 「最後の数字で場合分け」で解けます。
 しかし、コンテスト後、そういうツイートを見た後でもしばらく考えこんでしまった。

 たとえば、N=3で最後の数字が12なら、

・二倍する位置二つを1~3の中から選ぶ。
・三倍する位置を1~3の中から選ぶ。

 とすると、得たい数列が一通りに定まります。

 最初に問題を読んだとき、「倍数」という単語を見て、素因数分解はまず考えたはずなのですが、「最後の数字で場合分け」には思い至りませんでした。
 その後は、N=1から順番に数字を決めていくというDPを高速化することばかり考えてしまいました。

(それでもできる気がしたけれど、やっぱりダメでした……というツイート

E - Spread of Information

 木DPだというツイートは見たけど、他は自力でAC。

 コンテスト中は、問題を見て二分探索だろうとは思ったけど、「直径だけ調べていけないかな……」とだけ考えて、ダメなことを確認してCへ戻った。
 直径だけではダメでも、木の端の方から置かなければいけないということは分かっていたので、そのまま考えていれば程なく木DP(か全方位木DP)でいけることには気付いたと思う。

 ただ、木DPの実装にはあまり自信がない上DPの遷移もやや複雑なため、実装にかなり時間がかかってしまった。CをやらないでEに集中していたら通せてたか、というと厳しかったろうな。

F - Deque Game

 解説放送を聞いてAC。

 こういうゲーム問題では、相手の真似をする戦略が有効なことが多い。実際、数列が一つ(奇数長)で、最小値が中央にあれば、後手は先手と逆の方を取り除いていくのが最適になる。
 そういう風に考えると、

・中央あたりの数字しか関係なさそう
・先手・後手が入れ替わるかが重要なので、数列の長さの偶奇が大切そう

 といったあたりは思いつけると思う。(私は、ちょっとツイートなどを見てしまったので、ここまでも自力で思いつけたとは言えないけど……)

 ただ、偶奇に着目しすぎると、

・実は、長さ2と長さ4で方針が異なる

 ということに気付きにくくなると思う。これに気付き、さらに、

・長さ4以上で偶数の場合は、長さ3の場合二つのmin/maxへ帰着させられる

 ことに気付くところが難しい問題だった。
 言われてみれば自然なのだけど、私は解説動画を見るまで、長さ4のものをどう扱えばいいか分からなかった。

 長さ4以上のものがないときは、ゲーム問題の典型手法で解けると思うし、ACした人ももっとたくさんいたと思う。さらにちょっと捻られたときに対応できるかが問われている気がした。

2021年3月30日火曜日

AtCoder Regular Contest 115

 Cまで三完。そこそこ速かったためレートは下がらなかったものの、時間があってもDやEが解けておらず、気持ちは良くない。


D - Odd Degree

 公式解説放送を見てAC。

 まとめると、

・全域木に着目する。
・木の場合は、頂点の個数が同じなら答えは同じで、二項係数で求められる。
・木にさらに辺を付け加えた場合、一本ごとに全て二倍の個数になる。
・連結成分ごとに求めたものをマージしても計算量は二乗で収まる。

 といったあたりがポイントだったと思う。

 コンテスト中は、全域木を取ったときに木DPできそうだけど、上手くいかない……と悩んでいた。
 逆に言えば、一つ目のポイントには気付いていて、三つ目のポイントにも(理由は説明できなかったけど)気付いていた。

 あとは、二つ目のポイントに気付ければ答えにたどりつけたと思うのだが、近いことは考えたものの、上手くいかない気がしてしまった。

 だが、これは、ちゃんと実験していれば気付けたのでは? 
 手で実験するのではあまり多くのグラフを試しにくいが、愚直コードを書いていれば、実験するのはそう難しくなかったはず。
 愚直コードといって思いつくのは、辺の数Mに対して$2^M$通り試すコードで、確かにちょっと面倒くさい。とはいえ、書くのに五分もかからなかったのでは? と思う。

 ちょっと詰まった時点でこれをしていれば展開が変わったかもしれない。「愚直コードを書いてみる」というのは、今までしていなかったわけではないし、この問題でもしようか迷った時間はあった(が、手で考察が進む気がして避けてしまった)。
 だけど、今までよりもっと早い段階で、またもっと色々な問題で試すべきなのかもしれない。

E - LEQ and NEQ

 公式解説放送を見てAC。
 包除原理は確かに考えるかもしれない。
 (私は包除原理に慣れていないせいかピンと来なかったけど)そうすると、三乗のDP(けんちょんさんの解説の一番始めのDP)は思いつくと思う。
 それを高速化できる、というのは、解説放送を聞けば確かに、となる。

 ……が、それを実際に実装しようとしたら何をすれば良いか分からなくなり戸惑った。最終的には、具体例と合わせながらやれば書けたけども。

 包除原理を使う問題は、実装でも混乱しがちなのが大変だと思う。あまりさくっと書ける気がしないのだけど、これは包除原理が頭の中で整理できていないからなのだろうか……。

2021年3月28日日曜日

Codeforces Round #709 (Div. 1, based on Technocup 2021 Final Round)

  A一完で終了。レートを大きく下げてしまった。Cは解法はあっていたので、ACしなくてはいけなかった。


A. Basic Diplomacy

 時間がかかってしまったが、結構思いつきにくいと思うので仕方ないか。
 「m/2の切り上げより多い回使わないようにする」というこの値が重要。どういうときダメか、と考えると、友達の人数が少ないときの方がダメになりやすいが、全日程で友達1,2の二人を選べるなら、その二人で分ければOK。
 じゃあ、その二人のうち一人を選択しなくてはいけない日が、ダメにならないギリギリまであった場合どうか、というとそれでも他の日程で別の人を選べばOK。

 これがギリギリのケースなので、自明なダメなケース、つまり、一人しか選択する余地がない日がダメな回数ある場合を除けば良いとなる。

 ギャグ問題といえばそうだけど、「m/2回」という数字を見て、ほとんどの場合大丈夫なんじゃないの? という直感を持つことが重要、という意味では発想が必要な問題か。

B. Playlist

 「dequeを使う」というツイートを参考にAC。
 次に処理すべきindexをdequeでもってシミュレーションできるのですね、なるほど……。全く気付かなかった(し、言われても長いこと理解できなかった)けど、分かると自然ですね。

 シミュレーション部分は、各indexについて、「自分の左のindex」「自分の右のindex」のリストを更新していく感じで処理しました。(この方法が自然だと思うけれど、この代わりにUnion-findを使うこともできる)

C. Skyline Photo

 一目DP。
 高さhのもの(index i)を採用する場合、左右で自分より小さいものが初めてあらわれるindex l, rを求め、
・l~iのうち最大のもの+B[i]で、i~rの最大値を更新する。

 というのでOK。
 これは、遅延セグ木を使えばできます。

 結構すぐに解法は分かったのに、実装に結構時間がかかり、添え字のミスでWA。約15分色々試したけど、WAの原因が分からず終了。

 この問題だと、愚直を書けば分かったという気もしないので、見直し不足というしかないです。ただ、遅延セグ木の実装が間違っているのでは、と見直したのは無駄だったかもしれない。遅延セグ木ライブラリを適切に実装していればそこが原因ではないと思えたはずなので。抽象化すべきですね。

D. Useful Edges

 ACはしてないけど、解説は理解したつもり。

 全点からダイクストラ(もしくはワーシャルフロイド)を行った後、各辺について、全てのUsefulな条件を調べる……という方法がまず思いつくが、これだと、四乗かかる。(辺の数が二乗、Usefulの条件が二乗なので)

 ここで、重みwの辺ABが、path UVの条件に関してUsefulであるとすると、

・$UA+w+VB \leq l$

 と書ける。移項すると、

・$-l+UA \leq  -w-VB$

 となる。
 これは、端点の片方をVに持つようなUseful条件をまとめて扱えることを示している。

 つまり、全ての$V$を端点に持つようなUseful条件について、-l+UAの最小値を前計算しておけば、

・$最小値\leq  -w-VB$

 が成り立てばUseful条件のいずれかが満たされる、ということになる。

 この方法だと、前計算も、最後の計算も三乗なので、四乗から三乗に計算量が落ちている。

 ……ということで良いと思うのだけど、自分の書いたPyPyのコードはTLEしてしまった。頂点数600で、定数倍も結構思い三乗なので、ちゃんと高速化しても通るかは分からないです。あまり他の言語に書き直す気にならないので、ACせず置いておきます。

 式変形→前計算で計算量を減らす手法は結構使うと思うので、注意したい。

2021年3月27日土曜日

AtCoder Beginner Contest 197(Sponsored by Panasonic)

 全完135位。
 最初に解き始めたCでWAを出したり、Dでベクトルの向きや反時計回りが分からなくなった割には健闘したつもり。


 各問題について、ツイートしたこと以上に書きたいことがないので省略。


 CのWAは、あるbit列iで、位置jと位置j-1で01が一致しているか見たいときに、

・i & (1<<j) != i & (1<<(j-1))

 と書いてしまったためでした。
 両方1のとき(bitが立っているとき)、この両者は異なった値になるのでダメですね。前にも同じミスをしたことがある気もするので、気を付けないと。

2021年3月26日金曜日

Codeforces Round #710 (Div. 3)

 Gが解けずに終了。 


G. Maximize the Remaining String

 ツイートしたような嘘にハマってしまったが、貪欲で良かった。

 ただ、WAが出たとき、愚直を書こう、と思ったものの書き方が分からず諦めたので、(少なくとも自分基準では)思いつくのはそこまで簡単ではないと思う。

 「答えを決め打って貪欲」が正しい解法。
 これ、言われてみれば当たり前に見えるが、文字を消していく問題なので、「答えを決め打つという」ところに発想が必要だろう。
 とはいえ、正当性の怪しい解法(コンテスト中は証明できた気がしていたが……)でWAが出たら愚直解を探すべきで、どういうのが愚直な解法か、と考えていれば思いつけて良かった。

キャディプログラミングコンテスト2021(AtCoder Beginner Contest 193)

 Dまで四完でした。EFを見てFに行ったのですが、解けずに終了。Eの制約には気付いていたのに、やり方を思いつかなかったのも、Fの解法が思いつかず迷走してしまったのも大いに反省です……。


E - Oversleeping

 コンテスト中は主にFを考えていたので、こちらはあまり考えなかったのですが、Y,Qの制約が小さいのには気付いていました。しかし、周期性があるのなら、制約が小さくても何の意味があるんだろう? と思い、全探索を思いつかず。
 Y, Qを固定した場合の式を立式していれば解法に気付けたんでしょうか……。中国剰余定理まわりにもやや苦手意識があるので、ちょっと自信がないです。

・制約が小さいのだから、Y, Qを固定、全探索を考える
・Y, Qが決まっている場合を立式し、中国剰余定理に帰着

 のどちらのステップも自然な考察でたどりつけるものなので、できなかったのはまずいですね。反省。

F - Zebraness

 フロー。
 いわゆる「Project Selection Problem」(燃やす埋める問題)だった。
 フローと疑えば気付けるかもしれないけど、結構難しいですね。

 なお、PyPyだと(自分の)Ford-Fulkersonだと間に合わず、初めてDinic法を実装した。挑戦したことはあったけど理解できなくて今まで避けていたもの。とはいえ、分かってしまえばそこまで複雑ではなかったですね。

 「Dinic法」で検索してでてきたページ多数を参考にしてコードを書きました。ありがとうございます。坐禅Logさんtkwさんみさわさんなどを読んでいたらだんだんと理解できました。

2021年3月18日木曜日

Codeforces Round #708 (Div. 2)

  ABCとE1をAC。そこそこ速かったので順位はまあまあだったが、逆に言えば、時間があったのにDやE2が解けていないということ。


D. Genius

 コンテスト中の考察でほぼ正しく、後は詰めるだけ、というところまで行っていたと思う。
 ……が、実際にコードにするのには非常に長い時間がかかってしまった。こういう問題を解くと、大体の方針は立っても、そこから実際のコードに落とすのは容易でないことを実感する。

 (問題文の読解が結構面倒なので、ここまで考察するのも結構大変だけど)i→j、と進み、jが今まで行ったことのない、これまでで最大の数字のとき、この後進めるのは、

・iより前
・jより後ろ

 のいずれか、である。

 なので、DP[i][j]で、「一つ前がiで現在jにいるときのスコアの最大値」とすれば、メモリ二乗、時間三乗のDPで解ける。

 メモリ制限もあるので、これを一次元にまとめたい。(そうすれば時間も二次元に落ちそう)

 このとき、jを主役にして、
・DP[j]で「初めてjに到達して、直前にいたのがiのときの最大スコア」とするのでは遷移が上手く書けない。(私が書けなかっただけで、ちゃんとやれば多分書けるはず……)

 そうではなく、
・DP[i]で、「最大でjまで到達したことがあるものの中で、現在iにいるものの最大スコア」とすると、一次元のDPテーブルを使い回せ、遷移式も簡単に書ける。

 時間がかかっていたのは、前者の方針に固執してしまったせいもある。けど、その辺は、コードを書きながら遷移式を考えるので良い気がするんだけど。もっと先に考察すべきだったのかなぁ。

2021年3月17日水曜日

AtCoder Regular Contest 114

  ABの二完でした。解説を聞いてしまえば、C~Eのどれも解けてもおかしくなかった問題に思えるのだけど、実際には一つも解けていないので厳しい。
 (自分にとっての)難易度はE<D<Cだった気もするけど、どれに時間を割くべきか判断するのは難しいので、解ける問題を増やすしかなさそう。


C - Sequence Scores

 公式解説放送を見てAC。
 解説を聞いても難しく感じた……。
 
 本番中は、DPをメインに考えていたものの、各マスにおける寄与数みたいなことも考えた。けれど、まずN個の区間に分けた場合の総和からペナルティ分を引くという発想にはなれなかった。
 そもそも、

・同じ数字が二回現れていて
・その間の数字が全てそれより大きい

 ときにペナルティ、という数え方が思いつけていない。

 数列Aのf(A)を求めるとき、あるindexで+1される条件……みたいなことは結構考えていて、その際、「+1されないとき」を考えた方が上手くいきそう、とは思ったのだが、その条件を簡単に言い表すことができなかったのが敗因か。
 DPを念頭に置いていたとしても、このあたりは考えなくてはいけない内容なので、単純に考察が足りていなかったと思う。だが、この考察は結構難しく思える。

D - Moving Pieces on Line

 公式解説放送を見てAC。
 本番では、面倒そうだからと回避したけど、解法は意外とシンプルでした。

 ただ、T(赤と青の端点)の各点について、左が赤なのか青なのか、で場合分けが必要そうに見える(コンテスト中は、そこの考察が大変で時間がかかりそうだから避けた)ので、そこから抜け出すのが難しそう。

 また、その部分が考察できても、DPすると思えたかも自信がないので、本番中こっちを頑張ったとして解けたか、というと厳しいかも。
 ただ、地道な考察の積み重ねでACに結び付けられる問題に思えたので、考える価値はあった気がする。
 AC人数を見てDを考えるのははじめから避けてしまったけど、それはもったいなかった。(とはいえ、じゃあ何を参考にすれば良いかというと悩ましい。問題文を読んだ時点でいけそう、と思えたら解いてたと思うので)

E - Paper Cutting 2

 公式解説放送を見てAC。

 これはシンプルですね。
 ある直線での切断が行われる確率は? を求めようと思えば、解法に至るのは難しくない。
 四次元のDPテーブルを持つDPが見えるので、FFTなどを用いてそれを高速化していこう(という方針でも本当は解けるらしいけど)、などという方針に惑わされないことが重要だった気がする。
 Cと似た方針だけど、こっちの方がCより簡単な気がする。もっとちゃんと考えるべきだったか。

F - Permutation Division

 公式解説放送を見てAC。

 解説放送を聞くと、自然な考察の積み重ねで解けているように感じてしまったため、本当に難しいのがどこなのか今一つ理解できていない。
 もうちょっと考えてから解説を聞くべきだったか。

2021年3月7日日曜日

Codeforces Round #705 (Div. 2)

 ABの二完でした。Div.2でこれはひどい。


C. K-beautiful Strings

 後ろから見ていく。

 まず、各i文字目について、そこまでで各文字が何回ずつ使われているかを前計算。
 それを使って、「i文字目までSと同じ文字列で、条件を満たすものがあるか?」を調べていく。

 i文字目までに使われている各文字について、あと何回使えばkの倍数になるか? を計算する。それを埋めるだけでn文字まで埋まってしまうようなら、i+1文字目を、「その中でS[i+1]より大きい最小のもの」にして、後はソートして埋めるのが最適。

 そうじゃないとき、i+1文字目はS[i+1]より一つ大きいものになる。(S[i+1]が"z"のときは上手くいかないので、次の文字を見る)
 一文字付け加えたとき、「あと何回使えばkの倍数になるか?」を再計算。それでも余った部分は"a"を付け加え、ソートしたもので埋めるのが最適。


 ……というような感じで、コンテスト後にACした。
 「こんな感じ」という、おおまかな方針は立つけれど、正確に記述するのは難しい問題。上の説明でも、iが0文字のときなどを省略しているけれど、その辺を適当に書くと落ちてしまう。
 この問題は、おおまかな方針が立ったらコードを書きだしたのは間違っていなかったと思う(もっとしっかり考察した方が良かった、ということも多いけれど、この問題はそうではないと思う)けれど、詰まったときどうすれば良かったんだろう。難しい。

D. GCD of an Array

 解説を見てAC。

 エラトステネスの篩のような方法で、2*10^5までの素因数分解は前計算しておく。

 GCDはセグ木で管理できるので、素因数分解したものをセグ木に載せる方法を試したけど上手くいかず。

 公式解説を見ると、
・答えは単調増加なのだから、一々計算してはダメ。増えた分を差分計算しなさい、とある。

 なるほど、と思い、そこを直すも、セグ木のままだとTLEが取れなかった。(ただ、自分の書き方が悪いだけで、これでも通せるはずです)

 もう一度公式解説を読むと、各素因数について、最小値だけを管理すれば良い、とある。ただ、multisetを使う、という部分がよく分からなくて困った。

 結局、各素因数について、
・どのindexでそこを使ったかをsetで管理→setの要素数がn以上になれば、その素因数をGCDに持つ。
・(素因数の重み, index)をheapqに入れて管理、シミュレーション。heapqの最小要素が実際の値より小さいなら、popする。

 のようにしたところACできた。

 ただ、制限時間ギリギリなので、あまり良い書き方ではないですね……。(PyPyで、もっとまともな実行時間でACしている人がたくさんいます)

2021年3月6日土曜日

AtCoder Beginner Contest 194

 全完106位。久々に良い順位!


 各問題に関しては、書きたいことがないで省略。

 昨日のyukicoderと今日のABCとで連続で桁DPが出たのは珍しいですね。途中、実装に詰まったとき昨日のコードを見に行ったりしたので、そのおかげで早く解けたのは間違いないです。
 ただ、もっと綺麗に素早く桁DPを書く力を付けたい。

2021年3月4日木曜日

Codeforces Global Round 13

 Dまで四完でした。
 Cでセグ木を使ったけど、imos法を使えば使わなくて良かったらしい。確かに。
 Fは惜しかった気がしたけど、コンテスト後にACするまでかなり苦労したことを考えると現実的には厳しかった。


F. Magnets

 一応、自力AC。
 多分、公式解説と同じ方法なのですが、私が苦戦したところ(クエリ回数を減らす部分)に触れられていないので、何か勘違いしているのかもしれない。

 ここでは、ツイートしたものをまとめておきます。

 そもそものアイディアは、
・一個 vs 他全て
 をn回試すというもの。

 ただ、これだと、
・--N--NNNSSS
 のようなとき、Nのところに対しては0が返ってくるので困る。

 なので、そこをちょっと工夫して、「他全て」ではなく、「今まで試して0になったものを除く全て」を試すという方針にする。

 これは、まず、
・1 vs 他全部を投げて、
で0が返ってきたら、
・2 vs 他全部 - {1}を投げる。
これも0なら
・3 vs 他全部 - {1, 2}
……とやっていく。

 これなら、さっきの
・--N--NNNSSS
 という例で、最初のNでは0が返ってくるけれど、それ以降のN(やS)に対しては-1が返ってくるため上手くいく。

 それにより、n回のクエリで、「NかSだと確定しているもの」と「高々一個NかSがあり、他は"-"のもの」とに分けられる。後者を二分法で調べれば良い。
・NかSと確定している一個 vs 後者の半分
 というクエリを投げて、1が返ってくればその中にNやSがある。0なら、残り半分に高々一個NやSがある。

 ただ、この方針だと、n+lognの繰り上げ回数クエリが必要。さらに二回クエリを減らす必要がある。

 そのうち一回は割合簡単に減らせる。最初にn回クエリを投げたが、返り値の正負や偶奇を見ることで、最後の一回が"-"なのかNやSなのか、というのは分かる。
(具体的に書いていないのは、試行錯誤しながらコードを書いていたので、正確に理解できていないためです。逆に言えば、試行錯誤すればできるはず)

 もう一回は、先程書いた、

「後者を二分法で調べれば良い。
・NかSと確定している一個 vs 後者の半分
 というクエリを投げて、1が返ってくればその中にNやSがある。0なら、残り半分に高々一個NやSがある。」

 にヒントがあった。
 1が返ってきたときの方が情報が多い!

 なら、適当に半分を取るのではなく、
・NかSと確定している一個 vs 後者の半分のうち多い方
 というクエリを投げた方が得られる情報が多い。

 実際、そうすると、

・1が返ってくる→その中にNかSが含まれる(と情報が増えるので、最後1個残ったときそれがNかSと分かる。ので一回減る)
・0が返ってくる→「残りの方に高々一個NかSがある」(最後までこれを繰り返すと、残り一個が"-"かどうか分からないので、それも聞く必要がある。が、小さい方を取っていったので、logで収まる!)

 となる。

2021年3月3日水曜日

Educational Codeforces Round 105 (Rated for Div. 2)

  Cまで三完。Dが解けなかったのは、Cで苦戦して集中力が落ちていたせいかも。


D. Dogeforces

 ちょっと分かりにくいけれど、難しい問題ではなかった。

 小さい給料の人から順に木を作っていく。(与えられた表をx:給料、i,j:人として(x, i, j)のリストとみて、xでソートする)
 その際、最下層の人それぞれについて、「現在の木に関して、自分の一番上の上司は誰か」が分かるようにしておく。(Union-hindの要領で、再帰を使って書ける)
 すると、新たな給料xと人i, jを見る際、「iに関する一番上の上司もしくはjに関する一番上の上司」の給料がxであれば、新しいノードを作らないようにすれば良い。(そうでなければ、新しいノードを作り、その人を「i, jの一番上の上司」の上司にする)

 うーん、本番中は、(clarが来てたけど)解の存在とかが気になって思考が疎かになっていた気がする。特に発想もいらないし、できなきゃいけない問題でしたね。

E. A-Z Graph

 解説AC。
 解説を読んだらギャグだった……。

・頂点u, vの間に双方向のpathがあればkが奇数のときは条件を満たす
・さらに、そのラベルが一致していれば、kが偶数のときも条件を満たす

 となる。どちらも、u, vを行ったり来たりするpathを考えれば良い。

 ギャグだけど、最も単純なパスを考えれば答えに辿り着ける、という意味では教育的かも?