2021年1月8日金曜日

パ研合宿2020 第1日「SpeedRun」

 Dと最後三問が解けずの11完でした。


D - 立方体を壊せ!

 立体図形は苦手なので飛ばしたが、落ち着いてやれば解けることは解ける。

 まず、$x+y+z=K$という平面は、(K,0,0),(0,K,0),(0,0,K)を通ることをイメージする。立方体の頂点から各辺(座標)の方向に、切片が1ずつ動いていくイメージ。
 それができれば、$K<N$のときが三角錐で、$K>2*N$のときが、立方体から三角錐を除いたものだということが分かる。

 問題は、$N<K<2*N$のときだけど……。
 ちゃんと図を描けば、$K<N$のときと同じように作った三角錐から、角の三角錐三つを切り取ったものだと分かった。

 あまり立体が得意でない私でも分かったのだから、他の人でも分かるはず! と言うのは容易いけど、私より立体が苦手な人だっているはずで、そういう人にどう説明すれば良いんだろう。
 図を描くこと自体にそこそこ立体図形の能力が必要な気がするしねぇ。

 私自身、もう少し捻られたら自力で解くのが難しくなる気がする。(いや、色々断面描いて頑張るけど……)
 数学を勉強しても、あまりこういう立体をイメージする能力は伸びない気がする。厳しいね。

H - その計算、合ってますか?

 コンテスト中にWAを量産してしまった。
 各ビットごとに考えると、orが1のところでしかandやxorは1にならないことが分かる。

 あとは、「andが1のところは一斉に1を取る」ことに気付くのが重要。偶数回出現すればxorは0、奇数回ならxorは1になる。

 各ビットごとに考えていると解けない問題。
 とはいえ、各ビットごとに考えるのは基本なので、そう考えてハマってしまうのはある程度仕方ないか。

J - Output-only

「5, 12, 13」で検索したらこのページがでてきた。なるほど。
 見たときは何これ、と思ったけど、よく見れば自力でも思いつけそうな式か。

M - 貢ぎ物

 結構考えたけど分からず、解説を見てAC。
 「高速ゼータ変換」というキーワードを見たがどういうものか忘れていた。この問題のすぬけさんの解説を見たのは結構最近なのに……。名前と内容が結びつきにくいので、覚えにくいのかも。
 しかし、そのキーワードを見ても解けず。

 各$A_i$が$2^{20}$以下なので、$2^{20}$要素のDP配列を持つことは思いついた。だが、その配列を「その数を作れるか、否か」の01だけを持つものとしても上手くいかない。

 それを、DP[i]で「iに含まれる数j達で作ることができる最大の数」(つまり、DP[j]たちをorしたもの)とすると上手くいく。

 $2^{20}$要素の配列を持つというところはあってそうだけど、01だけを持つのは無駄なので、他に何か持てないかな……? と考えたら思いつけたのかな。

N - 背の順

 コンテスト後に問題を見て、自力でできるかと思ったら苦労してしまった。

 二つ以上の区間を消す必要はない。なぜなら、$[l_0, r_0], [l_1, r_1]$を消すなら、$[l_0, r_1]$を消した方が得だから。

 $[L, R]$を消す場合を考える。
 $L$の候補は、左端$A[0]$か、左から順に調べて$A[i-1]>A[i]$が成り立つ$A[i]$のどちらかになる。そのような$i$に対して、$j>i$なる$j$を消すと消した後がソート済にならないし、$A[1]~A[i-1]$を消すなら$A[0]$や$A[i]$を消した方が得なので。

 その後、$R$も同じように、候補が二個程度に絞れる……と思ってWAを出してしまった。
 左右対称な気がしてしまったが、実際はそうではない。

 $L=0$のときは簡単。
 $A[i], A[i+1], \dots $がソートされているような最小の$A[i]$が最適なRになる。

 もう片方の場合も、
 $R$は「$A[i], A[i+1], \dots $がソートされている」ような範囲の中にある。求める$R$は、この範囲で、
・$A[L-1]<A[R+1]$
 が成り立つような最小の$R$である。

 たとえば、

・$A=4, 1, 2, 3, 5$

 の場合を考えると、$L$の候補は$A[0]$と$A[1]$だが、$L=A[0]$に対応する$R$として最適なのは$A[1]$, $L=A[1]$に対応する$R$として最適なのは$A[3]$となる。


 公式解説がDPだったのは驚き。全く考えなかった……。

O - Xor Sum Sum

 解説AC。

 最上位bitから考える……といった方法も頭をよぎるけど、A,Bの数字が小さいので、なんらかの方法で全探索するのかな、とは考えた。

 が、基底を考える方針は全く浮かばなかった。言われてみればなるほど、という感じでした。
 xorの掃き出し法は、この問題この問題を解いたことがあったのにね。

 なお、xorの掃き出し法は、noshi91さんの方法が簡単。これで良いと納得するのには苦労したけど、一旦分かれば忘れない方法ですね。

2021年1月6日水曜日

Codeforces Round #694 (Div. 1)

 pretestはCまでの四完だったが、システムテストでCが落ちてしまった。


C. Strange Shuffle

 詐欺師(impostor)の周囲の値が変わっていくことに着目。右側はkより大きく、左側はkより小さくなる。「kでない箇所」は、左右一個ずつ伝搬していくので、kでない場所を探してから二分探索すれば良い。

 -1~+1、-2~+2、と伝搬範囲は広がるので、その範囲ずつ(つまり、大体2*ターン数)移動させていけば良い。

 ただし、詐欺師自身の値は変わらないこと、及び、最終的に変化しない状態になったとき、kの箇所は、詐欺師のものともう一つある可能性があることに注意しなくてはいけない。

 自分のシステムテストで落ちた実装は、n=4, k=2で、「2 3 2 1」となった最終状態でkでない箇所を探して、1と3を聞き続けるものになってしまっていた。2*ターン数ずつ移動させていたので、その二つしか聞けない!
 なので、移動する数を「1*ターン数」や「2*ターン数-1」に直したらACできました。

 なお、厳密にはこれでもHack caseがあるかもしれない。後半の二分探索パートでは、kなものを見つけたらただちにそれを答えとして出力するようにしているけど、「もう一つのk」がたまたま当たってしまうことはありそう。(本当にHack caseがあるかは分かってないです)
 多分、二分探索パートでkの箇所が見つかったとき、左右の数字も調べるようにすれば、そのケースも除外できるはず。

2021年1月5日火曜日

Codeforces Round #693 (Div. 3)

  pretestは全完でした。→システムテストも通っていました!


 ツイートだと読みにくいと思うので、Gだけ補足。

G. Moving to the Capital


Step1 都市1から各都市が何歩でいけるか(問題文中のdを)調べる。ここではDと書く。

Step2 それぞれの都市iから逆向きに一歩進んだ都市をjとして、ANS[i]=min(D[i],D[j])とする。高々一回だけ問題文中の2の移動をして良い、というルールを使った感じです。

Step3 各都市からDが大きい方へたどってみてANSの最小値が答え。問題文の1の移動でDが大きい方へは進めるので、これで答えが求まります。

 としました。

 Step3は再帰で書くのが簡単だと思う……けれど、CodeforcesのPyPyではこの深さの再帰は無理(エラーが出る)。(なお、AtCoderならエラーなしでいけるはずですが、PyPyの再帰は遅いので、間に合うかは分かりません)
 なので、どうしようか考えたけど、Dが大きい都市から見ていけば再帰なしでいけた。


2021年1月4日月曜日

東京海上日動 プログラミングコンテスト2020のD - Knapsack Queries on a tree

 東京海上日動 プログラミングコンテスト2020のD - Knapsack Queries on a tree

 を通したのでメモ。

 PyPyでは公式解説の方法ではTLEを取ることが不可能なようだ。(他の人の提出を見たけど、通せている人はいなそうだった)
 半分全列挙をlogを使わずにやるテクニックを用いると、PyPyでも通るらしい。(多分全ての)PyPyの提出はこの方法で通している。
 ……が、私が真似して書いてもなぜか通せなかった。

 ので、Kotlinで通そうと試みた。

 Kotlinだと、IntArrayなどを使って、最初から要素数を指定した配列で計算するのは速いが、mutableListOfとかで要素数を加えていこうとすると遅いようだ(良い方法があるのだろうか?)。
 なので、PyPyで速かった方法をそのままやろうとするとかえって遅くなる。
 そのため、公式解説の方法で、要素数を指定した配列のみを使ってやると通った。

 その言語での高速な書き方を理解していないと通せない問題は厳しい……。

2021年1月3日日曜日

AtCoder Beginner Contest 187

  年も変わったし、コンテストへの参加記録を書いていきます!
 ……といっても、コンテスト後、既に一日経っている。いつまで続けられるんでしょうか。


D - Choose Me

 何もしないと全員が青木氏に投票する。
 高橋氏が演説を行うと票がどれだけ動くか、と考えると、2*A+Bでソートすれば良いと分かる。

E - Through Path

 最初、全方位木DPが必要だと思い一旦飛ばした。
 Fを解いた後、やりたくないし書き終える自信がないけど、全方位木DPを書くかー、と書き始めたら、通常の木DPでいけることに気付いた。

 自然に書けば全方位木DP、「全体にxを足して、逆側からxずつ引く」ことを思いつけば普通の木DPでいける。

 とはいえ、全方位木DPでも書けるべき問題ですよね。

F - Close Group

 Nが小さいのでbit系を色々考えたが、しばらく思いつかず苦労した。
 「まず、完全グラフになっているものを列挙」を思いつけば、$4^N$のDPにはたどりつける。

 それが実は$3^N$になるというのは、この問題snukeさんの解説動画で覚えていた。難しい問題を解説ACしておいたのが役に立った! と嬉しかったのですが、Educational DP Contestのこの問題もそうだったのですね。解いたはずなのに全く覚えていなかった……。



2020年12月31日木曜日

2020年のまとめ

 2020年のレート遷移

・AtCoder 2030→2062(Highest 2030→2111)
・Codeforces 2155→2211(Highest 2232→2342)
・TopCoder SRM 1438→1423(Highest 1493→1721)

 SRMは一年前より下がっていた!


 一昨年、去年の大晦日のsolved数と比べると、

642→1952→3409

 で、去年解いた数が1310で、今年が1457。で大差なし。

 今年は結構頑張ったつもりなんだけど、そうでもなかった。結構難しい問題を解説ACしたと思ったんだけど、そういうのじゃAC数は増えないから、仕方ないのかな。
 特に今年前半は、このブログに書くため、今まで手を出さなかった問題まで手を出したつもりだったんだけど、途中から更新できなくなってしまったからね。来年はもっと簡略化(ほぼツイートそのまま、くらいでも)しても更新したい。

 レートはあまり上がっていないけれど、自分の気持ちとしては、青上位だったのが黄色下位まで来た、と思っている。上を狙えるところまで来たかな、と。

 特に得意分野も苦手分野もないつもりでずっといたんだけど、ABCやPASTの成績が同じくらいのレートの人に比べてやや落ちるので、苦手なのかなぁ、と思ってきた。青~黄色の人なら典型と感じるべき問題を典型と思えてないのかな、と思う。
 自分は、特に難しい問題を解くのが好きというわけではなく、どちらかというと簡単な問題をさくさく解くことに喜びを覚えるタイプなのに、典型問題に弱い(?)という成績になっているのはちょっと意外です。どうにかしたい。

 あと、最近、Python/PyPyで通せなそうな問題はKotlinを使おう、と決意しました。「型を書かなくてもいい」「;を書かなくていい」が大きい。この二つがないだけでストレスが大分違う!!
 典型問題攻略のためにも、ABCで苦戦した問題をKotlinで解きなおし……とかすれば良いかもしれないけど、データ構造の実装とかが面倒くさいんだよね……。今一つやる気が起きないけど、できたらやりたい。

 あとは、マラソン系(Codingameも)に手を出せたのは良かった。
 元々、アルゴリズムコンテストよりマラソン系(ハーフマラソンのような時間の短いものなら特に)の方が楽しいと感じていたので、そういうコンテストにはもっと積極的に参加したい。

2020年11月26日木曜日

Codingame「Fall Challenge 2020」

 Codingameの「Fall Challenge 2020」に参加しました。ルールはtsukammoさんのブログ記事が分かりやすいと思います。英語のルールを読むのは自分には結構大変で、この記事でルール概要を把握してから挑めたのはありがたかった。正直なところ、この記事がなかったら参加していたか怪しいです。


 結果は、全体513位/7011人、Gold League内で433位/464人でした。最終日にGold Leagueにはなんとか上がれたものの、そこで何もできず終わった感じですね。初参加にしてはまあまあ良かったとみるか、まだまだ頑張りが足りなかったとみるか。微妙なところですね。

 始めるとき、密かに目標していたのは、(あまり交流があるわけではないですが)ツイッター上で良く見かけるprd_xxxさん、AT274さんでした。prd_xxxさんは偶然(後述しますが、偶然としか言いようがない)上回れましたが、AT274さんはLegend League入りを達成され、ライバル視できるような状況には全くなりませんでした。それはちょっと悔しいです。


各リーグで書いたもの

・WOOD2

 BREWしかないので、貪欲にpriceから高いものから使っていきました。 → 一発クリア(11/13)

・WOOD1

 基本スペルが追加され、CASTできるようになりました。
 とりあえず、使えるアクションをランダムに選択したものを書く → ルピーを得られずダメそうなので提出せず。
 各成分の価値を1,3,4,5と決め、price/価値のもっとも高いものを作り、BREWするようにしました。 → これを提出してクリア(11/14)

・BRONZE

 ここでルールが出揃います。
 各成分の価値を1,2,3,4と普通に戻しました。

 まず、LEARNする呪文の戦略として、得られるコストの差分からtaxを引いたものが3以下なら覚えるようにしました。
 その後、盤面に対する評価関数を(適当に)作成。次のターンで評価値が最も大きくなるような行動を取るようにしました → クリア(11/19)

・SILVER

 一手先しか読まないのはさすがにダメだろう、とは思っていたので、再帰を用いてDFSで全探索するものを実装します。しかし、BRONZEのとき+1手しか読めず(これでもときどきTLEした)、もう一手読もうとTLEしてしまいます……。
 困ったものの、評価関数を多少直して提出 → SILVER内50位くらいまで上がる!

 さらに、ちょっとバグを修正して提出 → 順位が若干落ち、SILVER内103位。このとき、prd_xxxさんが102位で上下に並ぶ!
 しかし、時間が経つと順位が上がり、SILVER内15~20位前後に来る。ただ、対BOSSの勝率はあまりよくないため、GOLDに上がるのは厳しそう。

 そして最終日。あまり良い改善を思いつけていないまま、でもGOLDには行きたい、という気持ちで評価関数の調整や多少の高速化・バグ修正をしていると、朝7:30頃にGOLD昇格のお知らせが来た!
 提出していないのに、こんなことあるんですね。なんかこの頃、ボスと対戦させたら、(サーバーの具合で?)ボスがTLEして負けたりしていたので、そのせいかもしれません。

・GOLD

 GOLDに上がったコードはそのままGOLD250位あたりまで行ったが、その後だんだんと落ち、300位を割った。
 そのあたりで、修正していたコードを提出。明らかな改善はしていないものの、一応、前の提出には勝ち越しそうだった。 → GOLD450位付近(最下位付近)でも勝ち越せず、順位が上がっていかない。
 このとき、余裕がなかったので、前の提出の簡易なバグだけ修正したものを投げたりするも、やはり400~450位程度。さらに、元の250位あたりまで上がった提出を再提出したりしたけど、400~450位程度。

 この辺り、混乱してしまいましたが、まあどれも実力はそう変わらないはずなので、最初のコードも、時間が経てばGOLD最下位付近まで落ちたのでは、と思っています。

 でも結局、GOLDに上がった提出と新たに作った提出の良いとこどりをしたつもりのものを作って、最終提出しました。

やったこと

 基本は、評価関数を決めて貪欲。一手先を全探索して評価値を調べているけど、評価値を調べるときもう一回探索しているので、実質二手後を見ていました。

 評価関数は、

・素材の価値は30, 55, 70, 85(BRONZEの頃とは変えています)、スコアは200点。種類数ボーナス有。
・一つの素材が多くなり過ぎても意味がなさそうなので、個数で補正。
・序盤LEARNしやすく、後半LEARNを減らすため、CASTのlengthで補正。
・CASTの中身を評価。製造前と製造後の価値の差、repeatableか、製造前素材の価値が少ないほど評価(作りやすいので)、製造後の個数が少ないほど評価(作りやすいので)
・BREWしにくい組み合わせになってハマるのを避けるため、「今の素材から初期呪文でどれかBREWできるようにできるか? 大体、何ターンでいけるか?」で補正。

 あたりを考えて実装しました。

良かったこと

 普段競技プログラミングの問題を解くばかりで、こういう、ある程度長いコードを書いた経験がなかったので、それが書けたのは良かったです。
 普段はIDLE(Pythonデフォルトのエディタ)を使っているのですが、今回はVSCodeを使いました。色々とありがたかったです。特に、「定義した関数のところに飛べる」というのは有難いですね。
 IDLEは軽くて、電卓代わりに使うにはとても良いと思っているのですが、まともにプログラムをしようと思ったらエディタもちゃんと考えなければいけないですね。勉強になりました。

反省点

 コンテスト終了時に思っていたのは、
・どうやれば良いかはよく分からないものの、パラメータ調整のためにローカルで動かす環境を作らなくてはいけなかった
 というのが一番でした。

 しかし、他の方のブログやValGrowthさんの反省会を見て、ちょっと印象が変わりました。
 一番反省すべきは、ゲームの本質が分かっていなかったことかもしれない。BREWした後に素材を残し、repeatableな呪文を使って連鎖的に素材を作っていく……というのをあまり重要視していなかった。素材がなくなってもBREWした方が良いと思っていた。この辺りが分かっていれば(もしくは、検索などで調べていれば)評価関数をちょっと変えられたかもしれない。

 あと、高速化をもっとすべきでした。
 本当に高速化したいならC++など他言語への移植を考えるべきだけれど、Pythonのままでも高速化できるところはあった。どうせPythonのままだと限界があるので……という甘えがあった気がします。
 DFSのとき、全部のデータを送ってましたが、idだけにすれば速くなるはず、というのは気付いていたのに実装しなかった。

 単純なDFSじゃなく、枝狩りを入れる(これをビームサーチって呼ぶんですね)ことは考えましたが、上手くいかなそうと思ってしませんでした。これは、その時点の評価値だけだと、BREWのしやすさが分からず、枝狩りしてしまう可能性があると思ったためです。
 しかし、他の方の記事を見ると、今回はビームサーチが正解だったようです。

 連鎖させることの重要性が分かっていれば、BREWした後の素材の組み合わせが大事なので、評価値で枝狩りすることは有用だと思えたはず。そうしたらもっと深くまで探索するため、高速化を追及できたかもしれない。

 このあたり。技術力がなかったせいではなく、考えが浅かったため上手くいかなかったというのは悔しいですね。

 次回もし参加できたら、もっと良い順位を取りたいです。