/ 最近 .rdf 追記 編集 設定 本棚

脳log[20230319]



2023年03月19日 (日) [AtCoder] 今日は ABC294 があった。コンテスト成績証自分のすべての提出。F がほぼ既出の問題(水 diff)だったからなのかは知らないけど、6完でも渋いパフォーマンス。■D 問題「Bank」。この手の、前提知識なしで非効率的ではないデータの持ち方を考えさせる問題好き(賢くて効率的なデータ構造を知識なしで再発明することはもちろんできない)。「Notebook」とか「コンテナの移動」とかも同様。「Simplified Reversi」なんかは工夫した単純なデータの方が BIT を使った場合より log が落とせたりして最高(@chokudai「F問題とても好きな問題なんだけど、データ構造でいくらでも殴れちゃうのが残念……O(N)で解いてみてね><」)。実装しながらもずっと考えていて、検索してみても空振りだったのは、窓口に現れたということをメモする変数の名前。これが一番難しくて頭を使った。適切な名前が付けられたかどうかは知らない。他の人の提出を見て気が付いたけど、数字1つで済ませられるところで自分は配列を2本使う無駄をしてしまっている。■E 問題「2xN Grid」。数字の変わり目を2列で混合してソートして順番に見ていけば一致を数えられる。コンテスト中に不思議なことがあってね、サンプルの3だけ答えが合わないと思って、デバッグプリントを仕込んで数え違いがないかどうか順番に調べていったら手計算と完全に一致していたの。じゃあ自分の問題解釈が間違っているのかと思ってふと見たらサンプルの出力例とプログラムの出力も一致してるの。どこに間違いがあったのか。目か?■F 問題「Sugar Water 2」。「食塩水」とほぼ同じ問題。以前に解いています。「これまで何度も解こうとして解けていなかった問題。(中略) 最終的な濃度を決め打てば各溶液ごとに溶質の過不足が具体的な重さでわかるので……ということでした。」 自分では解けなかったんだね。■G 問題「Distance Queries on a Tree」。やることは難しくない。効率的にどうやるか。HLD の名前を聞いたことはありますよ。コンテスト中は「絶対に初心者でもわかるHL分解/Heavy-Light-Decomposition - Qiita」を読んでお勉強していた。見覚えがあるので以前にも読んだことがあるらしい。具体的な実装方法については知らないまま、同ページによると「かなり重い」らしいですが、雰囲気でまねごとをしてみた。ヌルポみたいなエラーを直せばサンプルが合ったので提出してみたら TLE×18/AC×55 だった。5秒で TLE なのなら頑張るけど 500 秒で TLE なら頑張ってもジャッジに負荷をかけるだけなので考えてしまう。■あ! 部分木のサイズでなく辺の重さで第一子を選んでた! ちょっと待って、修正する。提出 #39886338 (AC / 1797 Byte / 3935 ms)。制限時間ぎりっぎりではあるけど無事 AC でした。■AtCoder Problems に難易度が出たね。F が青 diff 上位なのに対して G が青 diff 下位で逆転している。パフォーマンスが渋いのはこのせいか。G 問題がしっかり崖として聳え立っていれば結果は違った。F 問題について、過去の水 diff が今日の青 diff であるなら、よくいわれる傾向とは逆の結果ということになる。■G 問題。オイラーツアーで解けると何か所かで見たけど解けるイメージがわかなかったので、もうちょっとしつこく考えてみた。オイラーツアーは部分木を一列に連続して並べる方法。辺の重さの変更を部分木にまとめて適用できると何がどうなる? 根からの距離が効率的に更新できる。根からの距離がわかると2頂点間の距離がわかるか? 「頂点 1 からのパスによって決まる値だけを考えて答えを出していい理由は、たぶん LCA までのパスが相殺されるからとか?」からの連想で、LCA を使って引き算すれば求められる気がした。提出 #39906499 (AC / 1407 Byte / 2456 ms)。短く速くなった。しかもバグが .keys 抜けが1つと L[] 抜けが3つだけと実装も素直。根からの距離を BIT で、LCA をダブリングで求めた。根からの距離をセグメント木で管理するなら LCA もセグメント木で求めたらいいと思う。BIT+セグメント木は無駄だし面倒すぎるのでナシ。だってセグメント木は BIT の上位互換でしょ?■提出 #39951562 (TLE×24)。試しにセグメント木で根からの距離の累積和と LCA の両方を管理してみたら派手に TLE だった。累積和は BIT でやる方が軽いのかな。■提出 #39954631 (AC / 3966 ms)。頑張りました。セグメント木にダミーの0番目の要素を入れて添字計算の微調整を省いたり、&:method 形式は Symbol#to_proc が呼ばれるせいなのか知らないけどやや遅いみたいなのでブロックを渡すようにしたり、せっかくセグメント木で累積和を管理しているのだから根からの距離を求めてから LCA を引き算するのでなく LCA からの距離を直接求めたり、オイラーツアーで作成する配列のサイズが 3N くらいになっていたのを節約して 2N に抑えたり。最初の AC だったなんちゃって HLD よりよっぽど厳しい。■モノイドが乗せられるようにまじめに ST クラスを改良したので、クラスを書く時のマイルールを。まずクラス利用者が知りたいであろう public インターフェイスを書いてから部外秘である private メソッドを並べる。public メソッドの中では、最初に呼ばれるであろうコンストラクタ(イニシャライザ)をまず書いて、つぎにアクセサ(読み取りメソッド)を書く。最後に、なくてもいい存在であり有害にもなり得るので書きたくないミューテータ(内部への働きかけ)を書く。クラスはオブジェクトのテンプレートであり、型通りの処理(不変部分)を書く場所だけど、ではオブジェクトのアイデンティティがどこにあるかというとコンストラクタへの引数でありインスタンス変数(メンバ変数)にふるまいを変える固有の値(可変部分)がある。すべてのメソッドがインスタンス変数に依存するべきだというのはそのため。その依存がないならメソッドの置き場所が間違っているサイン。とかとかいうことを考えながらクラスを書いている。あとはまあ名前だよね。よく言われるように名前重要。ソフトウェアという形のないものにはっきりした輪郭を与えるのは名前。適切な名前さえあればその実体をどう実装するかはどうとでもなるしどうでもいい。好きに、うまくやればいい。コーディング対象として適切な抽象に紛れのない輪郭を与える唯一無二の名前を見出すところが決定的に重要(形容詞増し増しで強調しました)。自分が常々疑問なのは、変数名に型名を使うというひとつの典型パターン(var arr = new Array; みたいな)。そりゃね、飼ってる猫が1匹だけならその呼び名はネコでも十分わかりますけどね、それは消去法や文脈でそう判断できるというだけであって、直接的な命名ではない。解釈や判断という余計なステップ、疑問や誤解の入り込む余地がある。クラスではなくインスタンスの、それそのものの本質を掴んだ命名をするのだ。■いやでも適切な命名は文脈に依存するということも言えるんだよな。識別子が機能するためには他と区別するのに十分な長さを必要に応じて使うのでいい。1文字変数を使い切ってから2文字3文字変数を使う、みたいな。ハフマン符号的な。その対極がたぶん Java の説明的で長ったらしい命名で、自分は HTML の p タグとか好きでいっぱい使いたくなる人間だからどちらかといえばアンチ Java なんだな。そのくせ、よそ者として文脈を共有しないで他人が書いたコードを眺めてるときに限って「変数名に型名を使ってる」とかを批判的に見てしまうわけだ。勝手な。