2020年1月31日金曜日

第6回 ドワンゴからの挑戦状 予選

 AとBはそこそこ早く解けたものの、それ以後何もできずに終了。主にCを考えていたけど、Dを考えた方が良かったか。とりあえず、黄色維持には成功しました。

コンテストへのリンク


A - Falling Asleep

 実装問題

B - Fusing Slimes

 期待値系の問題。ABC150のEやABC151のEと似た考え方で解ける。
 このときはABC150のEの復習が済んでおらず、問題を見たとき、「そのせいで解けなかったらどうしよう」と焦った。それより簡単な問題だった(と思う)ので助かった。

C - Cookie Distribution

・かけ算のままでは扱いにくいのでどうするか。

 コンテスト中はあまり良い方法が思いつかず、logを取ったらどうだろう? などと考えてしまったが、和と積が混じった式なので当然上手くいかない。

 解説動画のように、積を場合の数へ言い換えるのがポイントで、DPに帰着できる。maspyさんのブログを見ると、式計算でもできるようだけど、結構テクニカルに見えてしまう……。

 とりあえず、

・積を場合の数へ言い換えできることがある

 を頭に入れておきます。

D - Arrangement

 コンテスト後に考えたとき、解法ツイートが目に入っていたこともあって、ダメなパターンが、

1.(入力例2のように)残り二個で互いが互いを禁止している場合

 と、

2. 残っている数字のうちにaがあって、X以外の全ての数字が、Xを禁止している場合(は、まずXを選択しないとハマる)

 なことは分かった。厳密な証明は難しくても、ちゃんと考えればここまでは辿りつけそう。

 だけど、その後の実装も結構難しいと思う。

 私は、まず、リストAの各要素の出現回数をカウントしたもの(PythonだとCounter(A))をリストでもち、heapqにそれらの(カウント数, index)という組を入れて、最大値を管理しました。

 出現回数をカウントしたリストは常に更新していきます。
 なので、heapqで出てきた最大値がMでそれを取る要素がxとなっているけど、それがリストと異なっているときは、リストの方が正しいので、一度heapqから抜き、正しい値をheapqに入れ直します。
 それでも、2.の条件に抵触した場合に限り、2.の条件を適応(つまり、次をXにする)します。
 また、最後は、残り三要素を全探索することで、1.の条件を満たすようにできます。

 これ実装しているときは、こんな大変なことしなくちゃいけないのかなぁ、と思ったけど、今ここにまとめると結構自然な実装な気もする。(もっと簡単に書けますか?)

E - Span Covering

  解説動画を見てAC。

・とりあえずソート
 今回は、大きい順に見た方が分かりやすそう(早めに全体が埋まったら、後は簡単に計算できることなどから予想できる)
→制約を見るとDPしそう

 というところまではコンテスト中に考えていて、そこまでは正しかった。

 後は何を状態に持てばDPできるか? ということだけど、今回は、「区間が何個に分かれているか」「区間の長さの総和」を持てば良い。(それが分かれば遷移は書ける)
 確かに、DPで解けるとしたら、状態として持てるものはこれくらいしかなさそうか……。

 これが思いつきにくいのは、途中の計算が問題文の処理中に現れる場合の数と一致していないためだと思う。

 たとえば、入力例1では、最初に2の区間を置くけど、このとき(区間:1、長さの総和:2)のときの場合の数は1。全体5の区間に2を一個置く場合の数4とは異なる。
 なのに、最終的に、(区間:1、長さの総和:全体X)を調べれば答えになっているというのは不思議な感じがしてしまう。

 問題通りの処理をするDPではなく、途中は違っても、

・最終的に答えが一致するDP

 をしなくてはいけない。
 何を使ってDPすれば良いか、柔軟に考えるのが重要か。

 ところで、これと似たDPをどこかで解いたことがある気がしたんだけど、どこだったのかなぁ……。

2020年1月27日月曜日

Codeforces Round #613 (Div. 2)

 Dまでそこそこ早く解けたのでレートは上がったものの、二時間近くあってEが解けなかったのは辛い。

コンテストへのリンク


B. Just Eat It!

 「全ての連続部分列の和が、全体の和より小さい」かどうかを判定する問題

・連続部分列は、元の列から左右を削ったもの

→左右の連続する何個かの列の和が0以下になっていれば良い。
→が、和が負になるためには、片方0以下でなくてはいけないし、片方が負ならそれ以外の部分列をとれば良いので、左右からの累積和を見て0以下の部分があるか調べればOK

C. Fadi and LCM

  整数$X$が与えられるので、$LCM(a,b)=X$なる$a, b$のうち、$max(a,b)$が最小になるものを求める

・たとえば、8が$X$の約数のとき、$a$か$b$のどちらかは8の倍数になる。LCMを考えているので、片方に2、片方に4のように振り分けることはできない。ならば、中途半端にせず、片方に8を割り振るのがベスト

・なので、どの素因数を$a$に割り振るか調べれば$a, b$の値は定まる。

→bit全探索

D. Dr. Evil Underscores

yukicoderにそのままの問題があったらしいけど、自分はそれは解いてなかった。ただ、別のyukicoderの問題「No.911 ラッキーソート」に近いと感じた。
 そもそも、

・上位bitから0か1かを決めていく

 はXORの問題では重要ですね。

E. Delete a Segment

 けんちょんさんのブログやながたかなさんのブログに解説があります。

 これらの解説や公式解説で共通するのは、各セグメントを見るだけでなく、各時間における重なり合いを見ているところ。つまり、Hello2020のDで書いた「区間のままみるのではなく、時間ごとにみる」という発想の転換を使っている。

 私もコンテスト中にそのことは考えたのだけど、結局、それでそんなに簡単になると思えなかった。実際、そうしても実装は結構大変だと思う。(これらの解説を参考に書いたのがこれです。長くはないけど、「どこで+1するか」とかがややこしくてきつかった)

 だから、(私のコンテスト中の方針である)各セグメントごとに考えてもいけるんじゃないの? と思って、さっき通したのですが……。非常に大変でした。もっと上手く書けるのかもしれないけれど……。


 一応、このコードでしていることを簡単に書きます。

・とりあえずソートして、

・左右それぞれから、そのセグメントまで順に使ったときのUnionの個数のリストを作る

-左から考えるときは、i番目までの右端の最大値を使えば更新していける。(これをLEFTという名前にします)

-右から考えるとき(この名前をRIGHTとします)が難しい。

★
S[i] = [l,r] としたとき, 左端がr以上のものの中で一番左にあるセグメントが何番目かを二分探索で求める。→x番目だったとする

S[x]=rなら、RIGHT[i]=RIGHT[x]、ではなく、「RIGHT[i+1]~RIGHT[x]の最小値」がRIGHT[i]になる(図がないと分からないと思うけど、どういう図を描けば良いかよく分からないので省略。すみません)。
これを、(RIGHTを値として持ち、区間の最小値を得られる)セグメント木を更新しながら求めていく。

S[x]>rなら、RIGHT[i]は、RIGHT[x]+1、もしくは、R[i+1]~R[x]の最小値。
★


・これを使って、i番目を取り除いたときを考えたい。

-左側はLEFT[i-1]でOK

-右側は、i-1番目までの最大値を使って、★と似たようなことをすれば求まる

 これ、★というほぼ同じことを二回やっていて、その上結局、RIGHTの配列は使ってないから、★は一回にまとめられるはずですよね……。整理すればもう少し簡単になるはずですが、整理するにも頭が混乱してしまい大変です。

 方針としては自然だと思うんだけど、どうでしょう?

2020年1月25日土曜日

AtCoder Beginner Contest 150

 Dで混乱してEにいったらどちらも解けずに終了という散々な出来。Unratedで助かった。

コンテストへのリンク


D - Semi Common Multiple

・(入力例1の)6と10の場合を考えると、LCM+2*LCMかな? と思う

・だが、6と8の場合を考えると答えが存在しない。2で割ったときの偶奇か、2ベキが関係するかも?

 ……と、コンテスト中に考えて、どっちか分からず止まった。立式したらさらに混乱して、「拡張ユークリッドの互除法」を使うの? などと思ってしまった。

 実際は、「2ベキが一致するとき」を考えるのが正解で、2で割っていくことで2を一つしか因数に含まない場合に帰着していけば良いのだけど、ちょっと思いつきにくい気がする……。

E - Change a Little Bit

・とりあえずソート

→Sの種類が多いので、各Sについて考えるのは無理。Cの各数が何回使われるか、もしくは使われる確率を求めよう!

→今回は、Sを[0, 0, 0](0がN個)として考えても一般性を失わないので考えやすい。

 たとえば、Nが三個のとき、
$S=[0, 0, 0]$ に対して、$T=[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]$
 の八つ。コストが小さい数を後に使う方が良いのは明らかなので、

・index 2 の重みが何回使われるか考えると、SとTでindex 2 が異なるときの4回

・index 1 の重みが何回使われるか考えると、SとTでindex 1 が異なる四つ$[0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1]$のうち、

-index 2が同じ場合$[0, 1, 0], [1, 1, 0]$は一回ずつ,
-index 2が異なる場合$[0, 1, 1], [1, 1, 1]$は二回ずつ
の計6回

・index 0 の重みが何回使われるか考えると、SとTでindex 0 が異なる四つ$[1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]$のうち、

-index 1,2が共に同じ場合$[1, 0, 0]$は一回
-index 1,2のうち一つが異なる場合$[1, 0, 1], [1, 1, 0]$は二回ずつ
-index 1,2が共に異なる場合$[1, 1, 1]$は三回
で計$1+2*2+3=8$回、のようになる

 ここで、「index 1, ... , N-1のうちi個が異なる場合」というのは二項係数を用いて表せる。
 たとえば、「index 1,2のうち一つが異なる場合」というのは、Combi(2,1)

 これを用いてまとめると、
・index 0の重みがk回使われるのは、Combi(2,k)回と書ける。

 同じようなことが一般のNやindexについて言えることが分かるので、
$(*)1*Combi(x, 0) + 2*Combi(x, 1) + 3*Combi(x, 2) + ... + (x+1) * Combi(x, x)$
 のようなものが高速で計算できれば良いと分かる。

 ……と、ここまではコンテスト中に考えていて、ここからどうすれば良いかが分からず解けなかった。考えられる方針としては、

・式変形でどうにかする(OEISを使うことも視野に入れる)

・DP的な方法。$(*)$式で、x-1を利用してxを求める。

 などがあると思うけど、どういうわけかコンテスト中は二番目の方針ばかり考えてしまった。

 式変形でいけるとさえ分かれば、$Combi(x, k)=Combi(x, x-k)$なので、$(*)$式の左側からk番目と右からk番目のCombiは同じなので、まとめられる。そうすると、係数の和が同じ式になるので、

・$Combi(x, 0)+ Combi(x, 1)+ ... + Combi(x, x) =2^x$

 の公式を使って解決できる。

 式変形できないのかな? と真面目に一分くらい考えていればいけそうだと気付いたはずなので、

・式変形できないか試す!

 のが重要なのかなぁ。

 「方針が立っていたけど解けなかったとき」が一番悔しいんだけど、そういうとき何を反省すべきかも難しい。

 なお、youtubeの公式解説では、二項係数の計算をしていないけど、それはそれで難しい気がする。

F - Xor Shift

 自力では分からず、解説を読んでACしました。

・隣り合った二項のxorを考えると、文字列アルゴリズムに帰着できる

 がポイントですね。

 色々な式変形ができるのに、あえて隣り合った二項のxorを考えるためには、文字列アルゴリズムで何ができるかが頭に入っていることが大切そう。

 公式の解説動画を見てMP法を理解しました。うーん、原理は理解できても、0から実装しろと言われたらきついアルゴリズムですね。

2020年1月9日木曜日

Codeforces Round #612

 Aで時間がかかってしまって混乱したので飛ばし、B、Cを考えていたけれど分からずの0完……。

Div. 2 B. Hyperset


 $O(n^3)$だと厳しいので、どうやって計算量を落とすか、という問題(C++で枝狩りすれば$O(n^3)$で通るようですが)

・半分全列挙で$O(n^2)$に。

→本当に半分だと半分全列挙は思いつきやすいけど、$O(n^3)$を$O(n^2)$に落とすとき思いつきにくくなるので注意。

コンテストへのリンク


A. Garland


・(上手く貪欲すれば解けるらしいけど)制約を見るとDPが自然

→偶奇のどちらを使ったか、偶数・奇数それぞれを何個使ったかを持ってDP。これで$O(n^3)$

・コンテスト中は、i番目を見るとき、今まで使った偶数の個数+奇数の個数がiになることを使えば$O(n^2)$になるし、DP配列を使い回せばメモリ節約できる……とか混乱してハマった。

→この問題は制約が甘いので、$O(n^3)$で簡単に書けば良い。まあ$O(n^2)$にするだけなら良いと思うけど、後半のDP配列の使い回しは混乱を招くので避けるべきだった。

B. Numbers on Tree


・制約を確認!!

→これも制約が甘いので、単純な$O(n^2)$でOK。葉から貪欲にnodeの値を決め、今まで使った数字の間の値にしたくなったなら、それより大きい数字を一個ずつずらせば良い。

C1. Madhouse (Easy version)


・「全ての部分列」にはかなりの情報量が含まれるので、簡単に決まるのでは?

→1:nと2:nだけで決定できる!


Hello 2020

 今後、参加したコンテストにはできるだけメモを残しておこうと思います。(既に五日も経ってますが……)
 一応、解けた問題は解法を書きますが、厳密さは重視せず、箇条書きっぽく書きます。(解法ツイートの文字数制限ない版、という感じで)

 この回は、Dが解けず、Cまでの三完でした。

コンテストへのリンク


A. New Year and Naming


 実装問題

B. New Year and Ascent Sequence


 n個の配列から二つを選び連結したとき、$a_i<a_j (i<j)$ となる箇所が存在するものが何個あるかを全探索($O(n^2)$)せずに求める問題。

・そもそも、一つの配列に$a_i<a_j (i<j)$ となる箇所が存在すればどれと連結しても条件を満たす

・そうでないとき、その配列は大きい順にソートされている。

ので、条件を満たすためには、$a_1>a_2>a_3>...>a_n<b_1>b_2>...b_m$のようになっていれば良い.
→あとはソートして、二分探索を使うか、尺取り法でOK。

C. New Year and Permutation


 解法として思い浮かぶのは、

・DP or 数学的にcombinationなどを使う (or OEIS)

 といったあたりか。

→今回はDPは上手くいかないので、数学的に考える
→幅を固定すれば求まる

 幅$k$の配列がhappyになるとき、

- どの数字を選ぶか
- どの位置を選ぶか
- 中身の組み合わせ
- 残りの数字の組み合わせ

 を調べればOK

D. New Year and Conference


 D, Eはアルメリアさんのブログが詳しいです。私はこれを見て通しました。

・とりあえずソート

 は、良いとして、今回は、「開始時間でソートして区間を順番に見ていく」のほかに、「開始、終了時刻をまとめてソートして時間ごとにみていく」という方法があり、後者が正解。

・区間のままみるのではなく、時間ごとにみる

 という発想の転換が重要。

(区間のままでも解く方法はあるかもしれないけど分からなかった。遅延セグ木を使ったり、リストのHashを調べたりする解法もあったけど、この発想の転換は必須?)

E. New Year and Castle Construction


 偏角でソートは思いついていたけど、その後が考察できなかった。
 解説を読んで実装してみたけど、TLEが取れず。Pythonじゃ厳しい?

2020年1月3日金曜日

AtCoder黄色になりました、2019年の振り返り~2020年の目標

 年末のAtCoder Grand Contest 041で黄色になりました!


 また、上がり下がりが激しいのですが、Codeforcesでも薄橙に戻って新年を迎えることができました。


 約一年でAtCoder青→黄色というのは、早い方とは言えないかもしれませんが、自分としてはかなり上手くやった方だと思っています。一年前青になったときは、2020年をAtCoder黄色になって迎えられるとは予想していませんでした。早くとも一年半はかかるだろうと覚悟していました。

 AtCoderを始めて、色の感覚がなんとなく分かった頃から、黄色以上は天才という感覚がありましたし、今もあります。そこに一度でも到達できたということは非常に嬉しいです。

黄色になるまでしたこと

基本的には、この一年やってきたことは青以前と変わりません。

・コンテストに大量に出る→読んで解けなかった問題をできるだけ復習
・AtCoderのStreakを繋げる

 くらい。

 コンテストにはたくさん出ました(2019年のCodeforcesのコンテスト参加回数二位だったらしいです。えぇ……、自分でもちょっと引く)が、効率良く勉強できていたかは疑問。特に、Codeforcesでは解きっぱなしの問題が多くて、あまり身になっていないと思う。
 とはいえ、一年前と比べれば、復習する範囲は広がっています。700点くらいの問題なら解説ACはしなきゃ、と思えるようになりました。ただ、本当に手の届かなそうな問題にチャレンジしたりはしていません。

 AtCoderのStreakは、不慮の事故(リジャッジでAC→TLEになったと思っています。全ての提出がACになるバグが起きていた時期と近いので、まとめてリジャッジされたんではないかと。)で切りましたが、それ以外は繋げることができました。
 でもこれは、やる気のない日に競プロから気持ちが完全に離れてしまうことを防ぐためのものという感じですね。

 というわけで、特別な変化はしていません。環境も変えてないです。エディタもまだIDLEを使っているし……。

 なので、一年前に比べてはっきり実力が付いたかというと疑わしい気もするのですが、まあ多少安定感は増した気はします。一年前は、Pythonの書きやすさ等のおかげで青まで来れたけど、実際の実力は色一つ(レート400)分くらい下なのでは? という気がしていましたが、今はそこまでの差は感じません。たとえばC++でも、(今すぐは厳しいですが)多少練習すれば青パフォは取れる気がしています。

なんで早く到達できたか

こうした、あまり工夫のない方法で黄色まではいけそうだと思っていたし、実際到達できましたが、じゃあ、なんで早く到達できたか、というと、

・令和ABCが始まり、「レーティング更新対象: 0 - 1999」のコンテストが増えた

 ことが大きかったことは間違いないです。

 自分はそれほど令和ABCという相性が良かったわけではないですが、同じくらいのレートだった人がどんどん黄色に上がっていくのは刺激になりました。
 そして、そのせいで黄色になる実力のボーダーはやや下がったと思います。感覚的には大体レート100くらいずれた印象があります。以前のレート2000と同等の実力と主張したいなら、2100にはならないといけない気がしています。

 あと、

・10月から「ちはやふる3」のアニメが始まった

 ことが大きいですね。いや本当に。

 こういうスポコン系のアニメを見ると、「自分も努力しなきゃ」という気になります。本当のスポコンもの、スポーツで努力している作品を見ても、ちょっと遠い世界に思えてしまい自分を省みることが難しいけれど、こういう文化的な匂いがする作品は刺激になる。
  競プロ界隈のTwitterで「響け! ユーフォニアム」が話題になっていた時期(劇場版公開の頃です)に、「響け! ユーフォニアム」のおかげでやる気が湧いたというツイートを結構見た気がしますが、自分には「ちはやふる」の方が合っているみたい。(「ユーフォ」は好きだったけど、やる気が出るということはあまりなかった)
 いや、「ちはやふる」では、戦略性の高い個人競技で、元々才能がある人たちがさらに努力している姿を見せていて凄く良いと思う。刺激を受けています。(なお、百人一首は覚えていません)

黄色になる目安

さて、ところで。

 基本的には、AtCoder黄色の目安は、「700点まで全AC」(解説ACでOK。もちろん、解説を理解せずにACしたのではダメですが)くらいだと思っています。これでなれなくても、「800点まで全AC」をすれば9割以上の人は黄色になれるんではないか、と。
(Beatmaniaを知っている人向けに。これはSP難易度表の「地力Aや個人差Aのハード埋め」(よくSP皆伝の目安と言われるが、これを達成して皆伝を取れてない人も意外と多い)、と「A+ハード埋め」と大体対応していると思っています。
 個人的には、こういう風に他のゲームなりの経験を当て嵌めて考えた方が、自分の現在位置を把握する目安になって努力しやすいと思う。だからもっと多くの人に、受験勉強やらゲームやらスポーツやらとの主観的な対応関係を語って欲しい)

 実際は、AtCoderで黄色や橙になっている人のほとんど(もしかすると全員?)が、そこまで埋めずに黄色や橙になっている気がしますが、それは、やっている人が優秀だったり、他で数学やらパズルやらの経験をかなり積んでいたりするからではないか、と。

 実際自分も、700点問題はまだ半分も埋めていない(現状24/63)し、Codeforces等でそれを補う経験を積んできたかというと怪しい(Codeforcesでは、難しい問題は解説ACもできていないことが多い)。
 それでも黄色になれたというのは、他の経験が生きたんだろう、と思っています。ただ、自分の経験が多少なりとも役に立つのはこのあたりまで、という気もしています。

上を目指すにあたって

自分がさらに上へいけるか、というのはかなり疑問に感じています。

 そもそも、自分はどの分野でも(音ゲーもそうですが、音ゲーに限らずどのゲームでも、勉強関連にしても)、せいぜい黄色あたりの実力にしかなったことがない。

 競技プログラミングに関しても、自分のしてきたことは、ここか、もう少し上(高々レート2200あたり)を目指す努力だったと思います。もし、今後もっと上を目指すなら、このままではダメなのではないか、何か方針を定めたり、新しいこと(解説記事を書いたり、作問したりはしてみたい!)をしたりしなくてはいけないのではないか、という気がしています。

 ただ、他方では、別に新しいことをしなくても、「誰でも「800点まで全AC」くらいで黄色になれる」のなら、「誰でも「1000点まで全AC」くらいで橙になれる」のでは? とも思っています。
 しかし、現実には800~1000点あたりの問題は解説(や他の人のコード)を読んでもなかなか理解できないので、容易ではないですね。

競技プログラミングで差が出る部分

唐突ですが、競技プログラミングの実力向上に関して、一番差が出るのは、

・解説を読んで理解する能力の差

 だと思っています。以下、その説明のため、エセ科学みたいな怪しい内容を書きます。

 問題を解くことのみで競技プログラミングの実力を上げる場合、

・問題の練習量

 から、

・忘却量

 を除いたものが実力になると思います。
 なので、同じ問題セットを解くなら、短い時間で解いた方が実力は向上するはずです。忘却量が少ないので。

 ところで、最近、

・一問を解いたときの学習量

 は人によってあまり変わらないんじゃないか? と思うようになりました。「一を聞いて十を知る」というようなことはない。一問から得られる経験値は誰でもあまり変わらず、もし十を知ったように見えた人がいたならそれは、その人に既に他の知識があり、それと結びつけることができたため十を知ったように見えたんじゃないか、と。
 競技プログラミングを始める前はあまり分からなかったんですが、優秀な人の成長の様子等を見ているうちにそう思うようになりました。

 でも、同じくらいの時間と熱意で努力していても人によって結構差が出るように思えますよね。その差の一番の原因が、

・解説を読んで理解する能力の差

 じゃないかと。同じ問題セットを解いた(自力ACもしくは解説ACする)ときの実力向上はそこまで差が出ないけど、理解するまでの時間にはかなり差が出る気がします。

解説を読んで理解する能力

とはいえ、誰でも、(主に数学関係の)勉強する上で一番苦労するのが、この、「解説を読んで理解する」部分だと思うので、人によってどの程度差があるかはよく分からないんですよね。実力によらず、この部分が苦手だと思っている人は多い気がする(そういうツイート等もしばしば見かける)。

 今の自分の場合、AtCoderの公式解説を読んですんなり理解できるのは400点問題くらいまで。500点くらいの自力で解ける問題でも、公式解説をすぐには理解できないことは結構あります。また、700点~くらいの問題は時間をかけても理解できないことが多いです。

 まあでも、公式解説は結構簡潔に書いてあるし、こんなものか、とも思うのですが、ブログなど非公式の解説で(分かった後に読み返せば)ほぼ行間のないような丁寧なものでも、700点~くらの問題だとその場では理解できないことが結構多いし、理解できたとしても、一時間くらいはかかることが多い。これは遅い方なんじゃないかと。

 多分、この原因の一つは、自分が小説を読むように解説を読んでしまう、というところにある気がします。

 数学的な文章でも、まず小説を読むときのような読み方(緩く全体像を捉えよう、というような)をして、それで分からなかったときに、一行一行を論理を追うような読み方をする。で、論理を追っただけでは理解できず(した気になれず?)それを自分のストーリーに当てはめてようやく理解できた気になるんじゃないかな、と。
 だから多分、二度手間になってるんですよね。最初から数学的・論理的な読み方をして、数学的な理解=自分の理解となるなら、もっと早く理解できるんじゃないか、と。昔から悩んでいることなのですが、そもそも、頭の中の知識の整理の仕方が、他人と比べて論理的な配置になっていないような気がしているので……。
 理解が早い人は、数学的内容をそのまま(ではなくても、それに近い形)理解していて、そこら辺で差がついてるんじゃないかなぁ……。

解説を読んで理解しなさい

……と、ここまで書いてちょっと放置していたのですが、その間に、いや、さすがにこれは甘えてるのでは? と思えてきました。

・解説にギャップがあって理解できない
・解説であまりなじみのない概念(データ構造など)を使っているため理解できない

 のは仕方ないと思います。自分は、Union-Findや遅延Segment treeを理解するのに数日~一週間程度かかりました。まあ、そういう(分かってしまえば簡単なものでも)新しい概念を受け入れるのに時間がかかる(もしくは時間をかけても分からない)のは仕方ない。
 また、頭の中のデータ構造(?)に問題があるというのも、(その真偽はともかく)どうしようもない。
 けれど、

・適正問題で、特に未知の概念もなく、ギャップもない解説を理解できない

 というのはさすがにダメでは?

 実際問題として、そういう解説なら、ちゃんと数学書を読むときのように論理を追って読めば、(文体が合わなかったりして、なぜか理解しにくい部分があったとしても)長くとも一時間~二時間程度かければ理解できるのでは?
 そういう問題・解説を結構諦めてしまっていたのは、やる気がなかったからと言われても仕方ない気が。いや実際、論理をしっかり追って読むと疲労するので、途中で寝てそのまま諦めてしまうことも多かったような。

 そして、競技プログラミングでは、ありがたいことにそういう問題や解説を選ぶことができます。(その勉強しやすさこそ、競技プログラミングの一番の良さだと思っています。)
 適正問題かどうかは問題の点数やAC人数から分かるし、(どの問題も、というわけではないですが、1000点くらいまでの問題なら結構高確率で)ギャップの少ない解説を書いてくれている人がいます。

 レート2000前後という自分くらいの段階なら、解説自体が難解なほどの問題(の解説を理解すること)に挑戦する必要はないはず。なので、まずは、理解できるはずの(しなくてはいけない)解説をしっかり理解し、ACすることですね。
 これを繰り返せば700~1000点問題のかなりの部分が埋まるはずだし、それで橙近くまではいけるのでは?

 競技プログラミングに関してこれを今年の目標にします。

2019年1月22日火曜日

AtCoderで青になるまで

 新年一発目のコンテストでAtCoder青色になったのでまとめてみます。


2017年以前


 プログラミング経験はほぼないのですが、いずれできるようになりたいな、とは思っていました。一番の切っ掛けはAlphaGoです。今までできないと思われていたことが機械学習(やディープラーニング)でできるようになってきているんじゃないかと興味を持ち、どんなことができるのか雰囲気だけでも知りたいと、2017年にCourseraの機械学習コースに取り組みました。

 ……うん、まあ、雰囲気は分かったかも?

 一応、講座中のプログラムの課題も頑張りましたが、自力で解決できなかったものも多く、やはり実際にプログラムを書いた経験がないと辛いなぁ、と実感しました。

2018年4月 AtCoderを始める


 そんなこんなで、四月からプログラミングでも始めようと思い立ち、入門に何が良いのかな? と探してて出会ったのがAtCoderでした。知人でやっている人もいたので、それ以前から知る機会はあったはずなのですが、「AtCoder」という名前をちゃんと認識したのは2018年に入ってからです。

 そのときの実力は、

・数学 数学科出身なので、できなくはないはず! とはいっても、大学の数学は全然分からず、ほとんど身に着いていないのですが……。

・プログラム 基本的には未経験。講義などを受けたことは一度もなく、まともに勉強した経験はないのですが、昔々にN88-BASICに触ったことがあります。あと、C言語入門をざっと読みましたが、Eclipseでプロジェクトを一々作成するのが面倒くさくてやる気が起きませんでした(課題は全くしていない)。あとは、前述のCourseraの機械学習コースの課題をやった程度。
 
 という感じ。
 あ、ただ、P≠NP問題、ラムダ計算といったあたりは勉強したことがあります。



 AtCoderを始めるにあたって、AtCoder Beginners Selectionを解き始めたのですが、そのとき使った言語は、Courseraの機械学習コースで使っていたOctaveです。それまでプログラムには面倒くさい印象が強かったのですが、Octaveは直観的に分かりやすく、計算機の拡張という感じがあって、非常に好印象でした。

 今でこそC++も多少読めるようになりましたが、CやC++やJavaしか知らなければAtCoderを始めようとは思わなかったのは間違いないです!!

 ABC086C - Traveling(OctaveでACしている人もいますが、計算量的に辛い模様)以外はどうにか解き終え初めて臨んだコンテストが、AtCoder Beginner Contest 093。結果はCまでの三完、654位。

 ……まあまあでは?

 ただ、この回のDは700点問題なのですが、プログラミング的な技術を問わない純粋な数学問題で、数学の方の伸びしろはあまりないだろうし、勉強したところでこんなのできるようにならないのでは? と不安になりました。

 ところで、記念すべきコンテスト初提出が、これなのですが……バグってますね。後で全く同じコードを提出してACもらえています。

 コンテスト中は、

 なんでこれでTLEなんだろう?→コメントを消したら通るかな?→提出したら通ったやったー! コメントって消した方が早くなったりするんだね

 などと思っていましたが、そんなことはないですよね。マイナー言語でやるとTLEになることがあるらしいですね。

2018年4月~5月 緑になるまで


 最初のコンテスト参加後、Octaveはどうも不利らしいと知ったので、違う言語を探しました。でも、できるだけOctaveに似た言語がいいな~、と探して見つけたのがPythonです。
 これが正解でしたね!

 Octave以上に書きやすいし、(C++などに比べれば遅いとはいえ)PythonやPypy(同じコードでも大体Pythonより速く実行してくれる!)で間に合わない問題に出会うことはあまりない。AtCoderで青を目指すだけなら(多分、黄色も?)Pythonを選択するのが一番の近道だと思います。

 他の人のC++のコードを読めないことは多少辛かったですが、Pythonの提出も少なくないのでそれほど困りませんでした。今はさらに増えているので、Pythonで競技プログラミングをする環境はさらに整ってきていると思います!

 なお、IDEはずっとIDLEを使っています。行番号が表示されないのはちょっと嫌だけど使いやすいよ! 評判悪くて、ほとんど使っている人いなそうだけど……。



 というわけで、PythonでAtCoder Beginners Selectionを解き直し、次のコンテスト(ABC094)からはずっとPythonで参加しています。

 この頃は、AtCoderではコンテスト参加~復習以外ではほとんど問題を解いていません。その代わりに、paizaさんのスキルチェックの問題を結構解いていました。



 paizaのことは4gamersの記事(多分これ)で知ったのですが、当時は標準入力の意味が分からず挫折。いつかやってみたいと思っていました。AtCoderの問題に触れて、今なら解けるんじゃないかな、と。

 paizaでの最初の提出が4/10で、五月までに約五十問(うち七割がD問題)解き、ゲームもしたりと、Pythonの文法を身につける段階ではお世話になりました。5/21にSランク取得して以降も、頻度は落ちましたが新着問題を中心に解いています。

 ただ、コンセプト上仕方ないですが解説などないのと、同ランクの問題で難易度差が大きいためちょっと使いにくいですね。Sランクの問題は、大体AtCoder点数換算で400~600くらいのものが多いのですが、このとき解いた問題は200~300点程度です。

 特に、最近のSランク問題は難しいものが多いようなので、簡単な数問で失敗するとSランク取得の難易度は一気に上がると思います。



 さて、そうこうしているうちに、5月後半にはAtCoderで緑色にたどり着くことができました。

 ABCのC問題(300点問題)は、アルゴリズム等プログラミング力ではなく数学力が試される問題が多いので、言語に慣れればそれほど苦労せず緑色になれました。

 苦労せず、とはいっても、Zero-Sum Rangesが解けずAGC023が0完に終わったり、数学問題のFive, Five Everywhereを思いつけず、呆けてるなぁと実感したり、ABC097で初めてCが解けず二完に終わったり、と色々ありましたが……。



 ただ、200点/300点の差は、主に論理的思考力や数学力の有無を問う部分なので、人によっては大きなギャップを感じると思います。算数と数学の差に似ているというか(いや違うかも)。問題を解いているだけでできるようになるものなのかな……。人によっては300点/400点より苦労する気がします。



 そんな中、一番印象に残っているのはABC097のD - Equalsです。Union-Findを理解できず一週間近く苦しみました。

 ただ、そのおかげでデータ構造の面白さや意義に気付けた気がします。今思うと、競技プログラミングに真面目に取り組むようになった切っ掛けかも?

 今でもUnion-Findには愛着があります。

2018年6月~8月 水色になるまで


 この時期になると、AtCoderの問題の難易度や、レートの目安が分かってきました。本質的には数学なので、数学の問題とは一番比較しやすい。大学入試の数学の問題は、AtCoder換算で300~500点の問題がほとんどという印象です。700点クラスになるとかなりの難問ですね。

 また、レートですが、たとえば東大入試の数学だと、

 水色……60点くらいは大体いつも取れる
 黄色……90点くらいは大体いつも取れる

 くらいの印象です。

 青色≒東大合格という意見も目にしましたし、青になった今だとそれも納得できるところがありますが、受験数学には部分点があるのも大きいので、計算や答案の書き方の訓練をそこそこ積んでいるなら、理一・二合格は(数学に関しては)水色程度の力で十分だと思っています。

 また、Beatmaniaなら、

 水色……8段
 青色……10段
 黄色……皆伝

 というprdさんのツイートを見かけました。いや私は今でも、水色は八段より難しい気がするんだけどそうでもないのかな……。緑色≒八段、水色≒十段、青色≒中伝くらいな印象なのですが。とはいえ、黄色≒皆伝という印象は私も同じです。

 ただ、BeatmaniaⅡDXで皆伝になるまでに私は13年(3rd style~tricoro)かかってるので、黄色になるまでそんなにかかったら困るんですけどね! 時間がかかり過ぎてて客観的に見られないところがある。

 また、七月に、競技プログラミングをやっている知人に会って話を聞いたところ、その人が黄色だということが分かりました。



 以上と、自分の能力から考えるに、

 水色には、なれるはず。できるだけ早くなっておきたい。
 青色には、できればなっておきたい。
 黄色は年単位で努力を続ければなれるはず。

 くらいかな、と。

 そして、水色になるためにはABC全完しないときつい。今のままでは400点問題がほとんど解ける気がしない! 400~500点を取れるようにするため練習量を増やさねば!

 そう思い、他の競技プログラミングのコンテストに参加を始めます。
 具体的には、

 CSA 7/19から
 Codeforces 7/26から(マラソンコンテストには7/18から参加)
 (8/25に水色になっているので、以下は水色になった後ですが)
 TopCoder 9/19から
 LeetCode 9/9から

 という感じです。そして、AtCoder優先ですが、CodeforcesやLeetCodeには今も可能な限り参加するようにしています。

(※2019年6月、追記 LeetCodeの問題は就職試験からの転載で、著作権的にどうなの? という話があるようです。Contest問題はオリジナルだと思っていましたが、それも転載かもしれないようで。避けるのが良いかもしれません)



 とはいえ、参加数を増やすのは諸刃の剣ですね。
 練習量は増えますが、その分復習する時間を取りにくくなるし、生活は乱れます。ただ、他のサイトでもレートがつくのは楽しいし、続ける原動力になります。以前、囲碁クエストや将棋クエストに嵌っていたときも感じていましたが、レートがつくのは本当に楽しい! 特に競争が好きというわけじゃない(と思う)のですが、レートで客観的な自分の能力が分かり、努力してそれを上げていく……というのは非常に面白いです。



 しかし、今思うとこの時期は無駄に焦っていた気がします。
 水色に早くならなきゃ、と思ってしまったせいもあり、必要以上に緊張していたような。今よりずっと一喜一憂していました。三ヶ月ほどで水色にはなれたのですが、結構辛い時期でした。

 そんな中、八月あたりに自分用のライブラリを作り始めたことは良かった。何が切っ掛けだったか覚えてないのですが、前からやらなくちゃ、と思っていたことにようやっと手を付けた感じでした。
 頭の整理になったし、作ること自体も楽しかった。



 この頃、一番印象に残っているのは、なんといっても、ABC099のC - Strange Bankです。初めてのABC全完で嘘解法! 結構話題になった問題ですが、この嘘解法で通した人は他にあまりいないのでは……?

 今となっては笑い話ですが、当時は「最初の全完が嘘解法って良いの?」と、動揺してしまいました。
 でもまあ、これが切っ掛けになったのか、その次のABCでは無事全完でき、400点なら解けることが増えていきました。

2018年9月~ 青色になるまで


 水色になった記念に、昔作ったアカウントを掘り返してTwitter(@titia_til)を始めました!

 まず、「競技プログラミングアカウントだからプロフィール画像とかプログラムで作った方がいいよね」とPythonを用いてプロフィール画像を作成。なお、このプロフィール画像は、同心円っぽくなるはずが、計算間違いでアステロイドっぽくなって、あれ? と思ったやつです。

 そして、Python競技プログラマーを中心に気になっていた方をフォローしました。それまでも、コンテストごとにTwitterで情報集めはしていましたが、大分やりやすくなりました。たいしたツイートできていないけど、読んでくださる方に感謝。

 このブログも、その頃に解法を書いたり記録残したりしたいと思って作ったのに、一度も記事を書いてない!



 水色になるとABCではレートがつかなくなる、というのは大きな差ですね。私もARC102からARCに参加するようになりました。

 緑色の時代からARCに出るという人もいるようです。確かに、ABCの100点、200点問題は、緑色以上の人にはあまり練習にならないだろうから一理あると思いますが、私は水色になるまでARCに出ようとは思いませんでした。Cから解くとなると、一問もできないという不安と向き合わねばならないので……。

 そして、ABC onlyの回でレートがつかなくなると、レートがつくコンテストの頻度が結構下がってしまいます。頻繁に開催されるCodeforcesがモチベーションの維持に役立ちました。

 なお、水色になっても、競技プログラミングの勉強自体は、緑時代とほとんど変えていません。たくさんコンテストに出て、触れた問題(コンテスト中に読み、考えた問題)はできるだけ復習する、というのが主です。AtCoder換算で700点あたりまでの問題は解説ACしたい、と思っていますが、全部はできていませんね……。本を読んだりもしておらず、出会った問題ごとに調べているだけです。



 ただ、水色ということはABC卒業なのだから、ABCの問題は全部解けるようにならなきゃまずい、と思い、ABCを001から解き始めました。これが(難しいとは聞いていましたが)予想していた以上にきつく、勉強になった気がします。

 非公式時代のABCは、やや難しいけれど典型、という問題が多く出題されており、水色~青色で必要な知識を身に着けるにはちょうど良かった。このおかげで400~500点問題の正答率がかなり上がった気がします。
 600点以上の問題はいまだにほとんど解けませんが。



 あと、一応、敬遠していたC++の勉強も少ししています。
 IDEはCode::Blocksを使用。プロジェクトを作らなくて良く、すぐに実行結果を見ることができるものなら何でも良かったのですが、よく勧められているVisual StudioやVSCodeで開発環境を作るのは私には無理でした……。

 Code::Blocksなら(コンパイラーのセッティングで、C++17にチェックを入れることさえ気付けば)、ほとんど何もせずに上の条件を満たしてくれたのでありがたかったです。

 AtCoder Programming Guide for beginners の第一章は読んだので、C++の基本的な読み書きなら分かるようになりました! どうしてもTLEになる、というときは使いたいと思っています。

 ただ、やっぱりPythonの方が記述は簡単だし、「(Pythonで)TLEなコードを書けて、計算量的にC++では収まっている」ならACはもらえないけど実質(数学的には)正解じゃない? わざわざACコードまで書かなくても良いんじゃ? という気持ちもあるので、本当にいざというときにしか使わないと思います。



 そして、新年一発目のAISing Programming Contest 2019で、早めの四完により最高パフォ2400&青色到達しました!

 それまでの最高パフォは、CADDi 2018のときの2130だったのですが、このときは、D - Harlequinの嘘解法が通ってしまったおかげなんですよね。嘘解法で最高パフォというのはなかなかに屈辱だったので、それを払拭できたのも良かったです。

まとめ


 各コンテストについて。

・AtCoder……発想力重視の面白い問題が出題され楽しいが、典型手法を身に着けるにはあまり向いていない

・Codeforces……雑多な出題で、面白い回もあればそうでない回もある。系統立てて勉強するには向かなそう。難読問題も多い。

・LeetCode……ABCよりやや難しいくらいの難易度。典型問題が多いので勉強向き。ただ、標準入出力じゃないのにちょっと戸惑う。特に、Binary Treeのclassを扱う問題がよく出るけど、これって一般性あるの?

 という感じ。

 TopCoderはDiv.1とDiv.2の難易度が離れ過ぎている(最近はそうでもないようですが。私はDiv.1に上がって三回連続でeasyも解けなくてやる気が落ちた)印象があってあまり出ておらず、出題傾向等は分かってません。

 青になって十日ほど経っていますが、現在のコンテスト参加数は、

AtCoder 43回(非公式コンテストやマラソンコンテストを除く)
Codeforces 42回(Ratedのみ)
LeetCode 19回
TopCoder 4回
yukicoder 3回
Hackerrank 1回
Hackearth 1回

 三桁超えてるんですね。AC数は、

AtCoder 489
CodeForces 193
TopCoder 3
LeetCode 69
AOJ 9
yukicoder 11
Hackerrank 2
Hackearth 11
paiza 約120

 合計2000ACになる頃には黄色になれたらいいなぁ……。
 今は、ABC埋めが終わったら、ARCかLeetCodeを埋めるのが良いのかなぁ、と思っています。



 緑色のときくらいから、「自分にはもうほとんど伸びしろがないんじゃない?」とか、「次の色まではなんとかいけても、二つ上は不可能なんじゃない?」とか思ってましたが、青色になってもまだまだ勉強・練習すべきことは残っているし、ありがたいことにまだ多少の伸びしろはありそう。

 また、競技プログラミングは他の勉強より効果が現れやすい。
 というのも、解けなかった問題を解説ACするとき、解説を読んで「頭の中で再構成して」コードに直す、という工程を経るので、一問一問理解して次へ進むことができるから。それに、他の人のACコードが読めるなど、解説を読んで理解できなかったときの勉強の材料も揃っている。

 なので、今の自分に適正な問題(AtCoder換算で500~700点あたり)を300~500問くらい解けば、次のステップにいけるはず、という気がしています。

 青色は一つの目標でしたが、先が見えているうちは先へ進みます。



 あ。
 当初の動機だった機械学習系には、競技プログラミングに嵌ってしまったこともあって、一切手をつけてないのですが、それもいずれ……?