/ 最近 .rdf 追記 設定 本棚

脳log[2024-06-02~]



2024年06月02日 (日) [AtCoder] 今日は AtCoder Regular Contest 179 があった。結果が見たいような見たくないような気がするけど、どちらにしろまだ出ていないだろうからまずはふりかえり。■A 問題「Partition」。サンプル1の出力例が壊れていましたね。解説文と食い違っていた。サンプルの2がなんで No になるのか考え込んでしまった。Yes だよね、「(Yi​<K かつ Yj​≥K) ならば i<j が成り立つ」という型の命題は、前提が成り立たないときは常に真だよね、誰か質問しないかなと考えていた。そのうちに問題文を何回目かに読み返していたときに気がついたんだけど、累積和の初項が必ず0だから、その時点で K 以上の値が出現してしまっている場合があるのだね。K が0以下の場合とそれ以外で場合分け。ここではあっさり書いたけど、境界値が 0 なのか -1 なのか 1 なのかでもじっくり考え込んでしまった。基本的には Yes でソート列が答えになる。判断が分かれるときは正の和と負の和の絶対値の差と K を比較する。ここでも不等号にイコールが付くかどうかをじっくり考えてしまった。なんなら最初に戻って場合分けの条件から考え直している。これが自分の脳みそに収まるぎりぎりのサイズの問題です。■B 問題「Between B and B」。ある文字が要求されているいないの状態が 1<<M あって、これを N 文字分繰り返す DP をするのだと思った。実は最後の文字が何であったかを区別しようとしていて、そうすると1億ステップの処理になって TLE なのではないかと思ったけど、区別しないなら 1000 万なので間に合いそう。どちらにしろ数字合わせができなくて TLE かどうか以前の問題だった。■C 問題「Beware of Overflow」。個別の値の絶対値が R 以下ですよ、値の合計の絶対値も R 以下ですよ、二項間の比較と加算を駆使してどの瞬間どの値も絶対値が R を超えないようにうまい順番で全体を1つの値にまとめましょうね、という問題。最初はソートしてから先頭と末尾の加算を繰り返せばいいかと思った。ソート列を真ん中で折り返して加算するので、1回のイテレーションで全長がおよそ半分になる。だいたい答えは合っていたけど、3ケースだけ WA だった(#54171399)。どういうケースで良くないかというと、-R,-R,-R,2,2,R-1,R-1,R-1 みたいな場合? R=3 だとすると 2+2 をしてはいけない。改良版では、加算した結果を二分探索で適切な位置に挿入することで常にソート済みの状態を保ちながら、最大値と最小値の加算を繰り返すことにした。提出 #54175146 (AC / 442 Byte / 177 ms)。■水パフォで喜べないって悲しいね。わざわざ確かめないけどね。自分の想像では ARC、ABC、ABC、ARC と下げが続いている。■■■D 問題「Portable Gate」。Ruby で挑戦している人がいたのでまずは問題を読んでみた。全方位木 DP? ゲートを置く場合と置かない場合のコストを積み上げていくのかと思って実装を始めたけど、どうもそれではいけない。始点に帰ってくる必要がないのだ。最後に選んだ辺へは行きっぱなしでいい。そうすると、ゲートを置かずに帰還する場合のコスト、ゲートを置いて帰還する場合のコスト、帰還しない場合のコスト(※ゲートを置く置かないは呼び出し元に影響しないので置けばよい)の3つを考える必要があるのかなと考えたところでギブアップ。知力を振り絞るためには頭の良さだけではダメで外国で冬の海に飛び込めるくらいの体力が必要らしいですよ(もちろんそんな体力もエネルギーもないのでさっさと投げ出しちゃいます)。■通った!!! 提出 #54283484 (AC / 1521 Byte / 1758 ms)。びっくりマークを3つ付けてもいいでしょう。たいへんだった。寝る前に数時間がかりで慎重にコーディングをしたら、ちょっとしたシンタックスエラーを2つ直しただけでサンプルが通って、あとのミスは親方向の深さを計算するときに +1 するのを忘れていたことだけだった(37 行目と 50 行目。子供から見て兄弟は最も近くに見えて2親等だという話)。結局考えるべきは先に挙げた3つに加えて、(ゲートを使わないで)帰還する場合と帰還しない場合の差分として最も遠い子孫との距離を記録した。この問題ならではのフックがありました。全方位木 DP の手順として、全体から子を引いてそれを親方向のパラメータとして子に与える必要があるんだけど、パラメータの計算に最大値(最小値)を使っている部分がある。つまり、複数の子があるときに、1つの子については帰還する必要がないけど、他の子についてはすべてを黒に塗ったあとで戻ってくる必要があって、その選択に最大値(最小値)を使っているんだけど、子の影響を全体から差し引くときに最大値(最小値)が利用できなくなる場合がある(その子がまさしく最大値(最小値)だった場合)。こういうときのトップ2戦略は過去2回の経験がある(20240302)。どうですかね、もっとシンプルに解ける考え方があるんですかね。選び方次第でパラメータが減らせる可能性はあるかな。全方位木 DP というだけでたいへんなのに、それに輪をかけてたいへんだった。パラメータが4つあってそのうち2つでトップ2戦略を使った。最後に補足。ゲートを使う使わないを辺ごとに独立に選んでいい理由は、ゲートを使わずに網羅して帰還する辺を、ゲートを使って網羅して帰還する辺より先に選んだことにすればいいから(そして最後が帰還しない辺)。■■■B 問題。提出 #54413597 (AC / 433 Byte / 1544 ms)。すでに散々悩んだあとで頭の中が整理されていたからか、フツーに普通の DP だった。日記に「ある文字が要求されているいないの状態が 1<<M あって、これを N 文字分繰り返す DP をするのだと思った」と書いたことは忘れていて、ブロックされている文字にビットを立てる DP をした。それで良かったけれど時間が厳しい。3重ループになるのだけど、N のループの中で 1<<M のループの中で M のループを回すとコードテストで 10 秒弱かかった。制限は特に長めでもない2秒。ここで ABC356-D Masked Popcount の解法のひとつ、ビットごとに 0 と 1 の出現サイクルを利用するものを思い出した。ビットごとに 0 または 1 だとわかっている数についてだけループを回すなら、3重ループの最奧でビットが立っているかどうかの判定をしなくて済むし、ループの回数も半分になる。そのためにはループの順番を変えて、N のループの中で M のループの中で 1<<M のループを回す。遷移はビット演算が2つ。ある文字を置いた結果その文字自身をブロックし、そののちいくつかの文字のブロックを解除する。■同じく B 問題。提出 #54414039 (AC / 796 Byte / 1628 ms)。Ruby による B 問題へのすべての提出を見ると Akiyah さんが 2047 ms で AC まで一番近づいていた>#54319004。お楽しみを奪ってしまったとしたら申し訳ないのだけど、速くなりそうな部分があったのでそこを修正したものが冒頭の提出。具体的には ns.sum{|n| dp[n] }dp.values_at(*ns).sum に変更したら 20 % くらい速くなって AC になった。インタープリタ型の言語は、できるだけ短い指示で多くの仕事をコンパイル済みのネイティブコードにやってもらうのが良い。ちなみに TLE を避ける工夫は自分のものとは異なっていて、1<<M 通りの遷移先をビット演算で N×M 回求める代わりに前計算をして、遷移先ごとに遷移元の集合を用意している。それがさっき出てきた ns。そういう準備があったからこそ dp.values_at(*ns).sum という効率的な遷移を書くことができた。遷移を1行で済ませることができていた。■■■D 問題。解説を読むと、読むのもたいへんなので8割読み飛ばしちゃうんだけど、移動コストの上限は 2(N-1) で固定だから、ゲートを使って帰還コストをどれだけ節約できるかだよと書いてある……ような気がする。提出 #54485687 (AC / 989 Byte / 1765 ms)。パラメータが1つ減って3つになった。解説の最後にリストされている3項目に3つのパラメータを対応付けるなら、順番に RG+LG になるのではないかな。最後が一致しているのでヨシ!


2024年06月01日 (土) [AtCoder] 今日は AtCoder Beginner Contest 356 があった。自分のすべての提出。結果は見ないようにしつつふりかえりだけ。■A 問題「Subsegment Reverse」。愚直に配列を操作してもいいし、区間を3つに分けてもいい。■B 問題「Nutrients」。Array#transpose が便利。■C 問題「Keys」。愚直にやります。2**15*100 は数百万なので間に合う。問題文の「与えられるテスト結果が誤っており上記の条件を満たす組み合わせが存在しない場合もあります。その場合は 0 通りと解答してください」の意味がよくわからなかった。信頼できない入力が混じっていて、それを見抜いて何らかの対処が求められているのかと思った。実際は単純に矛盾するテストによって答えが0通りの場合があるよということを言っているだけだった。たしかに、複数のテストを照合した結果矛盾が生じて答えが存在しない場合、じゃあ、個々のテストにおいて「開いた(開くはずがない)」「開かなかった(開かないはずがない)」という結果をどう扱えばいいかという疑問が生じる。それは誤りであるという断り書きだった。■D 問題「Masked Popcount」。かつてよく見られた数え上げ問題。苦手だよ。でも精進によってなんとなくの型みたいなものは持っている。上限が N で与えられてるのがやっかいなんだよね。たとえば N=2**60 ならこれは主客転倒で寄与を簡単に数えられる。じゃあどのようにしてそういう状況を作るかというと、与えられた N のビットのうち、最も左にあって最初に倒されるビットを決め打つことによって N を複数の区間に分割する。N のプリフィックスと自由に {0,1} が選べるサフィックスとで構成される区間について問題を解く。たとえば N=0b10101 ならプリフィックスは1のビットの数と同じ次の3つ(0b10100、0b100xx、0b0xxxx)。 もちろん1つもビットを倒さない場合も忘れない。■E 問題「Max/Min」。よくわからない問題。N の上限は 20 万。これは O(N) もしくは O(NlogN) が解法となるよくある制約で、N の制約としてはほぼ上限。そして A が取り得る値が 100 万以下。これは N と比較すると大きい数字だけど、処理すべき区間が 100 万以下であってそれ以上は考えなくてもいいと思うと、そんなに大きい数字には思えない不思議。結局これは調和級数の上限がどうのという問題だったんだろうか。特別な道具を使わずに配列を操作すると決めたあとでも答えを合わせるのに苦労して、結局1時間近くかけてしまった。やるべきことはひと目でわかるんですよ。すべての組み合わせについて足し合わせるに当たって、予めソートして Ai と Aj の大小関係を固定してしまえば考えるべきは分子が分母の1倍か2倍か3倍か……。時間が厳しいので(そこが問われている問題なので)同じ値はまとめて計算しますよ。そうするとどんな入力が嫌かというと、小さい数字が順番に並んでると、処理をまとめることができず、区間を大きくスキップして処理することもできないのが困るなと。だけどね、100 番目、1000 番目の値はすでに十分大きいわけです。100 万割る 100 は1万だし、100 万割る 1000 は 1000 なので、個々の処理量は無に等しい。もちろん1万が1万集まれば馬鹿にならないんだけど、全体で見ても処理可能な範囲に収まることは知っている。名前は出て来なかったけど高3の演習問題でいつの間にか不等号を証明させられていたような気がする。■F 問題「Distance Component Size Query」。解けなかった。セグメント木とか平方分割とか、うまく区間を分割して効率良く数えられないかと考えたけど、できそうでできなかった。でもできるのかなと思ってる。自分にはできないけど。K=0 と K=1、それと K がすごく大きいときでだいぶ様子が違ってくるなとも思った。でも、じゃあ K=1 の場合に限れば問題が解けるかというと、そうでもなかった。■■■D 問題。桁ごとにサイクルを考えるという解法を仕入れてきた。LSB から見ていくと、サイクルは2、4、8と倍々になっていく。その中身だけど、前半が0で後半が1という単純なもの。だから 0 から N までの N+1 個の数をサイクルと半端に分けて1の数を数えるのも簡単。提出 #54162917 (AC / 165 Byte / 42 ms)。この考え方だとずるいぐらいに実装が簡単だった。自分は自分の考え方で実装したとき式を間違えて1回 WA を出してるからね>提出 #54107427 (WA)。


2024年05月25日 (土) [AtCoder] 今日は東京海上日動プログラミングコンテスト2024(AtCoder Beginner Contest 355)がなかった。いいね、そんなものはなかった。だから残念な結果も存在しない。少なくとも自分は確認していない。だけれども問題はあり、解けた問題もあるので、ふりかえりは行う。■A 問題「Who Ate the Cake?」。Ruby の配列は集合演算も行えて便利。■B 問題「Piano 2」。A 数列と B 数列をマージしてソートして、A 数列由来の要素が連続しているかどうか。愚直に線形探索をしても許される制約。どうとでもやる。■C 問題「Bingo 2」。1列が最大 2000 要素。縦横斜めが最大 4002 行(列)あり、それぞれに対して 2000 要素のスキャンを行ってもおよそ 800 万なので許される。ビンゴカードに何ターン目に呼ばれるかを書き込んで、行(列)ごとに最大値を読み取る。呼ばれない番号もあるので、それはテキトーに T より大きい値にする。Ruby で 800 万の処理はちょーーっと不安だったので、ターン数を知るのに Hash を使う代わりに Array を使うようにした。他所で読んだけども、制約がひと桁以上大きければ、ターンに沿ってビンゴゲームを進行し、ビンゴが成立したかどうかの判定を行列対角ごとに用意したカウンターで行うしかなかった。■D 問題「Intersecting Intervals」。タイムラインに沿って入りと出を管理して、現在を中に含んでいる区間の数を数える。数がわかれば組み合わせが数えられる。サンプルをよく読めば書いてあったけど、閉区間なのである値で隣り合っている2つの区間は共通部分を持つ。同時に起こる入りと出の処理順に注意する。■E 問題「Guess the Sum」。ABC349-D Divide Interval の記憶も新しいので(20240413)、今度はセグメント木をすぐにイメージすることができた。でも解けなかった。クエリ回数を最小化しないといけない。そのためには、与えられた区間を2冪に分割するか、もしくは、与えられた区間を内包する2冪から区間外を除くか、どちらを選ぶ場合も考えられる。それをうまく整理できなかった。散々悩んだ末にまずは DFS 関数を2つ書いた。1つは区間を受け取ってクエリ回数を数える関数で、もうひとつはクエリを実際に実行する関数。積み上げるのがいいか取り除くのがいいかは、2冪の幅を大きめと小さめの2種類特定して、再帰呼び出しでシミュレートして比較選択した。この2パターンでいいということが全然見えなかった。「整理できなかった」とはそういうこと。その結果、ひと通りの実装は済んでもデバッグの時間がとれないまま時間切れになった。穴あきになっていた PAST の問題(魚釣り)の精進をしてから(ふてくされてたんだよ)、コードの整理とデバッグをした。再帰関数はクエリ配列を返すもの1つだけに統合された(#53902777)。最短のクエリ配列を選んで最後にまとめて実際のクエリに変換する。クエリ配列を使うものも使われないものも区別せず生成破棄を繰り返すのは無駄が多いけど、それでも許される制約なのだから AC のためにはこだわっていられない。■F 問題「MST Query」。解けてないよ。MST というのは辺のコストの昇順に UnionFind をするやり方しか知らない。それでクエリに答えられるか考えてみたけどできなかった。辺のコストが 10 通りの値しかとらないところがクサい。つまり、コストの更新機会が限られるのではないか。グラフは最初は木で、クエリのたびに辺が増えて入り組んでいくけど、実は常に木の形を維持していていいのではないか。そして木を維持するのにちょっとばかしコストをかけても、総体としてコストの和は実行可能な範囲におさまるのではないか。新しい辺を追加しても木の形を維持するというのは、2頂点間のコスト最大の辺を特定して切断するということ。そんなの逐次的に効率的にできないよ。UnionFind の経路圧縮的な手抜き手段がないと、部分木のすべての頂点について親子関係を更新することになってやってられないよ。■自分のすべての提出。成績証は確認しません。■■■UnionFind を 10 個使うんだという解法の核心を某所 publish.obsidian.md/naoya/ で読んだので、それでどうやって答えが出せるのかをお風呂で考えてきた。UnionFind ではコスト1のグラフ、コスト1から2のグラフ、コスト1から3のグラフというような 10 種類のグラフを管理する。あとはまあ、やります。提出 #53927502 (AC / 576 Byte / 1512 ms)。当日は、コスト1のグラフ、コスト2のグラフみたいに分けることは考えていた。「それでクエリに答えられるか考えてみたけどできなかった」。あとちょっとが足りないんだよなあ。やるだけも門前払いも退屈なので、これはちょうどいい難易度。■AC を出したことなので安心してネタバレを読みに行ったら、「ABC355 振り返り」、なんか UnionFind の使い方が違う。Ruby の中ではひときわ速い fumta さんの提出 #53927847 が同じ解法だと思うんだけど、えー、わかりません。自分の解法は、最初に N-1 本の辺のコストの合計を記憶しておいて、新しい辺が採用されたらそのコスト w を足し、それまでに採用されていたが不要になった辺のコストを w+1..10 の範囲で探して引くというもの。単純明快でしょ。しかも、コスト1の辺を使うグラフ、コスト1から2の辺を使うグラフ、……という 10 種類のグラフを維持管理する処理と、辺を追加したことで不要になった辺のコストを特定する処理が一体となっているところが美しいと思うんだ。これは解答というより問題の力だとは思うけども。■■■E 問題。kotatsugame さんの動画(【競技プログラミング】ABC355【実況】)でグラフとしての見かたを知ったので、新規に実装して提出した>#53993734 (AC / 1045 Byte / 402 ms)。変数の取り違えで RE を出し、off-by-one エラーで WA を出し、デバッグ版を提出して WA を出したあとでの AC だった。やっぱり難しいね、この問題。ミスのしやすさも問題の難しさのうちだとすればね。やっていることは辺が陽に与えられないだけの BFS。グリッド問題みたいなもんだ。■C 問題。行や列をスキャンする O(N^2) 解が 455 ms (#53855978) だったところ、カウンターを使う O(T) 解が 132 ms (#53993977) だった。制約がちょっと変則的で、1≤T≤min(N^2,200000) かつ 2≤N≤2000 なので、T の上限が 20 万なのに対して N^2 の上限が 400 万。N^2 解を許容するためにあえて制約の桁を減らしていることが窺える。C 問題だしね。まんまと甘えさせてもらいました。


2024年05月22日 (水) 驚異(キョウイ)脅威(キョウイ)が区別されていないと感じる文章が少なくない。自分は驚異=wonder、脅威=threat と結びつけることで区別している。この対応自体が Wonders of the World (en.wikipedia.org) の訳は世界の七不思議 (ja.wikipedia.org) じゃないよね、七大驚異だよねという認識から来ている。細かいことをいうとこの用例での Wonder は自然や人が作り出した絶景(驚嘆すべき構造物)という限定された意味を持つらしいので驚異では不十分なのだけど、そこの事情は英語でも変わらないっぽい? つまり、wonder が特にそういう限定された意味を持つというわけではなく、限定された意味を持たされた用例が存在しているだけなのだろうということ。元はギリシャ語にあって「眺めるべきもの」を意味する言葉だったがラテン語を経由するときに意味が足りなくなり英語を経由したときに意味が転んでしまったみたいな経緯が日本語のウィキペディアに書いてある。人間は概念を直接やりとりすることができず概念に与えた恣意的な輪郭である言葉をやりとりしているがゆえに異言語間ではずれが生じる、みたいな。異なる人間と人間のあいだに共通概念が存在できるのかどうかもわかりませんが。ともかく、脅威に対しても wonder に対しても驚異って単語が出て来ないせいで誤ってしまうのは認知度が低すぎるので、意識して脅威≠驚異=wonder の関連付けを行っているという話。


2024年05月18日 (土) [AtCoder] 今日はパナソニックグループ プログラミングコンテスト2024(ABC354)があった。8時半をまわってから2回もハードウェアエラーでブルースクリーンが出て、Rated で参加するのをためらったけど、コンテスト中に再起動が必要になることはなくて助かった。ていうか、再起動してもダメなんだよ。ブートデバイスが見つからないって出る。つまり、起動中にいつの間にか C ドライブを見失っていて、それがスリープ移行中や復帰中に発覚するとブルースクリーンが出る。それ以外のタイミングだと IO 待ちで一部のプログラムがフリーズするのみで何もできなくなるまでにわずかの猶予がある。いずれにしろ再起動しても C ドライブを含む SSD は見つからないまま。SATA ケーブルを抜き差ししてお茶を濁すということを1年以上前から続けている。数回のブルースクリーンでパーティションが飛んでいた FAT32 と比べて一度も壊れたことがない NTFS の堅牢さは素晴らしい。もちろん、ファイルシステムが壊れていないからといって個々のファイルが壊れないわけではないのだけど。という話はもういいや。結果がまだなのでふりかえりから。■A 問題「Exponential Plant」。やります。朝と夜の区別が地味にやっかい。脳のリソースが奪われる。サンプルの解説文に何を判断基準にするか、何を答えるかが書いてあるのでそれを素直にコードにする方が早かった。■B 問題「AtCoder Janken 2」。やります。A 問題より素直。■C 問題「AtCoder Magics」。強さの順もしくはコストの順に処理をする。今見ているカードを基準にして捨てるカードを捨ててから追加する。スライド最小値の要領。■D 問題「AtCoder Wallpaper」。前回の F 問題 Tile Distance と似た見た目の図があるけれど、D 問題ということで素直に計算できるのかなと期待する。2倍した面積を答えるのだから、三角形の数を数えればよい。X 軸方向の周期は4で、 Y 軸方向の周期は2だから、単位面積当たり計8通りのパターンがある。あとはそれぞれの数を数える。自分は頭より先に手が動くタイプなので、スマートな式を考え出すよりも case 式で場合分けをいっぱい書いた。場合分けの数だけバグが潜む場所が増えるのでリスキーではあるが、考えても答えが出て来ないリスクも無視できない。■E 問題「Remove Pairs」。先に F 問題までひと通り目を通してから実装を始めることにしている。泥沼にはまりかねない D 問題に対して E 問題は素直にメモ化再帰をするだけだと思ったので先にこちらを解いた。6分半で書いた最初の提出が TLE で、そこからペナルティ込みで 17 分余計にかかったのが残念。ABC349-E Weighted Tic-Tac-Toe が解けたら解ける。■F 問題「Useless for LIS」。左右から LIS をやって自分の左の増加列と右の増加列の長さ+1が、自分を含む増加列の長さ。これが最長と一致しているかを確かめる。LIS のやり方を忘れていて思い出しながらの実装だった。でも、LIS というのはトランプ挿入ソートを解いていたこのとき(20200602p01)に、名前も実装方法も知らないで解法を考え出した経緯があるので、思い出せないはずがない。忘れてもでっちあげられる。■日記を書いていたら結果が出た。コンテスト成績証自分のすべての提出。1895 の青パフォで +33、レートは 1631 (青色) になりました。初めて青色になりました。ここ3回の ABC、ARC、ABC で +52、+24、+33 というのはできすぎた結果なので、すぐに水に落ちた後で水青の反復ができるとも思ってないよ。


2024年05月13日 (月) 呼び出し元と呼び出し先の境界たる関数のシグニチャは、引数の型は、プリミティブなものを第一にすべきだという考えを持っている。関数の本体で高機能で高コストな便利クラスが使いたい? 使えばいいよ。でもそれは実装の詳細であって、呼び出し元の知ったことではない。逆についても言える。新しく関数を定義してコンテクストを分けるとき、関数の内部が特定の、現在は唯一のでもあるかもしれないがそうであっても、呼び出し元の影響を受けすぎるべきではない。呼び出し元は便利クラスの殻をむいてプリミティブな実体を渡すように努めるのが良い。何がプリミティブかという部分で、判断のすりあわせが必要になるかもしれない。■呼び出し元に無駄にオブジェクトをコンストラクトさせるなという話であり、呼び出し先が必要としないオブジェクトを無理に押しつけるなという話であり、関数の内と外が DLL という形でバイナリ境界をまたぐときに malloc/free に互換性があると思うなよという話。


2024年05月12日 (日) [AtCoder] 今日は AtCoder Regular Contest 177 があった。コンテスト成績証自分のすべての提出。AC までの所要時間が、A=5分、B=8分半、C=20分。計34分で3問解いておしまい。昨日に続いて Highest 更新は嬉しいんだけど、焦らしますねえ。あと +2 で青タッチだよ。青になって青を維持するためには ABC-F が半分以上の確率で得点できないと無理だと思ってるよ。今は精進で6割埋められるくらいかな。得点にできていない。ではふりかえり。■A 問題「Exchange」。DP でさえないし、愚直に1枚ずつカウントして許される制約。日本の硬貨は5倍2倍5倍2倍5倍と規則正しく増えていって完全に小が大をかねるので、使いにくい大きい硬貨から貪欲に使っていく。■B 問題「Puzzle of Lamps」。左からしか操作ができないので、一番右から目当ての状態を作っていく。■C 問題「Routing」。赤の道と青の道が X 字に交差するので、どちらの色でもある紫色を最低限だけ配置する。最初はちょっと難しく考えすぎていた。赤にとっての最善というのは、(1,1)が属する赤の島と(N,N)が属する赤の島を赤の島を経由しながら紫の道で連結していくことで求められる。それは 01BFS でできる。青の道の最善も同様にできる。で、考えすぎていたのは、それぞれにとっては必ずしも最善ではないけども、共通部分を大きく取ることで全体として紫の数を最小化できるケースがあるのかなと思ったこと。そんなケースはない。赤にとっての紫の道は必ず青を変化させているし、逆に、青にとっての紫の道は必ず赤を変化させている。お互いの紫の道に共通部分はない。じゃあ 01BFS を2回やってコストの和を答えにするだけ。■D 問題「Earthquakes」。DP ですよね。前からの値と後ろからの値を突き合わせて答えを出すのかなと。2^N 倍した数を答えにするというから、確率ではなく場合の数を足し合わせていったものがそのまま答えになるのかなと。正しい答えが出て来ませんでした。


2024年05月11日 (土) [AtCoder] 今日は AtCoder Beginner Contest 353 があった。コンテスト成績証自分のすべての提出。E までの問題を読んでから A から順番に書き始めた。A 問題の AC が9分30秒時点だから、だいたい9分くらい問題を読んでいた。それぞれの問題の実装時間が、A=30秒、B=4分、C=11分、D=5分、E=11分、F=36分(読解含む)。C 問題が難しかったよね。ではふりかえり。■A 問題「Buildings」。ループを回せますかという問題。Ruby を使っているなら、ループという汎用的で原始的な道具ではなく、ループを使って何がしたいかという目的別に特化したメソッドを選んで使う。意図が明らかなコードを書く。C++ でも <algorithm> の中に何があるかひと通り調べておいて、使う機会を常に窺っておくのがいい。■B 問題「AtCoder Amusement Park」。前から順番に K 人を超えない範囲でグループを作っていくといくつになりますかという問題。やるだけではあるんだけど、テストがないと不安がぬぐえない。祈って提出しました。K を超えた数を数えていた場合、最後のグループを数え忘れるというのが、考えられる罠かな。■C 問題「Sigma Problem」。CDE と続く Sigma Problem の第一弾。二重のシグマは見慣れていないとびっくりするけど、シグマのそれぞれの範囲を見れば、考えられるすべての組み合わせについて足し合わせるという意味だと読める。基本的には answer += a*A.size+A.sum while a = A.shift なんだけど、2要素の和が 10^8 を超えるときは 10^8 を引かなければいけない。A 数列の順番には意味がないので、予め A 数列をソートしておいて、何個の要素が 10^8 を超えるかを効率良く数えられるようにする。二分探索なら log が付くけど間に合うし、尺取りっぽい操作をするなら log は付かない。あっ。自分の提出 #53343786 で 10**8 に付けた名前が P(rime number) というのは嘘だ。■D 問題「Another Sigma Problem」。今度は2要素を文字列として連結してから数値として評価をする。順番に意味があるので並べ替えてはいけない。右側にある要素について、数の和と、下駄の和がわかればいい。下駄というのは、3桁の数字であれば 1000、4桁の数字であれば 10000 を計上する。これを全要素について数え上げておいて、更新しながら答えの計算に利用する。■E 問題「Yet Another Sigma Problem」。かかった時間だけ見れば C 問題と同じなんだけど、配点通り CD より難しかったと思う。自分は Ruby の記述力におんぶにだっこで効率の悪い解答を書いた。まず、N 個の要素について、先頭の文字を見ます。同じ文字だったもの同士をグループにして再帰的に次の文字を見ます。その過程で、グループの大きさを見ます。大きさが Z なら、作れるペアの数(Z*(Z-1)/2)がそのまま答えに寄与します。■F 問題「Tile Distance」。わかりやすい図が付いていてたいへん助かります。K=1 は簡単。マンハッタン距離が答え。それ以外はどうしましょう。最初はフラクタル的に考えてみようとした。スタート地点とゴール地点が隣接していると見なせるまで K を定数倍してみる。で、視点をズームインしながら移動コストを足していけないかと。できなかった。次は大きいタイルと大きいタイルのあいだの移動コストを数えようとした。横方向の移動コストだけを考えてみる。K が大きいなら、上下にあって頂点で接している大きいタイルを経由することで、必ずコスト4で真右にある大きいタイルへ移動できる。K=1 のケースはさっき除外した。K=2 の場合は小さいタイルをそのまま突っ切って移動する方がコスト3なので安い。そういう計算をしているうちに、斜めの移動コストが特に安いと気がついた。K マスを1と数える大きいタイルの座標系で考えてるよ。例えば右に2、下に2の位置にある大きいタイルに移動したいとする。右に移動してから下に移動するなら、さっき計算したコストを使って 3+3 (K=2 のとき) か 4+4 (K>2 のとき) になるけど、ありがたい図を使って数えてみると、たったの4で右下の右下にある大きいタイルに移動できることがわかる。というわけで、まずは斜めに移動して、それから縦もしくは横に移動すると考えると、最適な大きいタイル間の移動コストが求められる。あとは1または4通りと、1または4通りの総当たり(1x1 または 1x4 または 4x1 または 4x4 通り)でほぼ答えが出る。1と4が何かというと、スタート地点ゴール地点から最寄りの大きいタイルの数。「ほぼ」が何かっていうと、スタートとゴールが小さいマスにあってすぐ近くにある場合。これは K=1 の場合のマンハッタン距離と同じだから、提出 #53370051 では K=1 の場合を分けるのをやめて雑に一体化した。全部混ぜ混ぜして最小値を取れば自ずと答えが出てくる。■G 問題「Merchant Takahashi」。今日の G 問題は F 問題と配点が同じ。F 問題を通してから G 問題を読んで、することがなくなったので順位表を見たら F より G の方が通されていたね。自分は G 問題がさっぱりだったけど、データ構造で一発なぐるだけの典型だと仮定して眺めてみれば、移動コストによる報酬の減殺が組み込めるなら、セグメント木が使いたい形だと思った。もちろん組み込めないのだけど。■Highest を更新したけど、明日の ARC の参加費は何レートかな。何レート吸われるんだろう。3-4-5-(略) という配点は水色の自分向けド真ん中なので出ないわけにはいかないんだよな。■■■精進。G 問題。セグメント木を2つ持つんだと2か所で読んだ。それでどうやって距離による減殺を組み込めるかを考えた。左右の端、頂点1と頂点 N にいると仮定したときの所持金のポテンシャルをセグメント木の各要素の値とすると、イーブンな条件で大小比較ができて最大値が取り出せる。提出 #53408933 (AC / 849 Byte / 1999 ms)。2秒制限で 1999 ms は神がかっている。だめだったらセグメント木にブロックを渡す代わりに max メソッドの呼び出しを直に埋め込むだけ(手動インライン展開)。■実際の値の代わりにイコール条件のポテンシャルを扱うのは ARC120-C Swaps 2 でやったことがある。20211022p01。なんでそんな名前で呼んでるのかは自分でもわからない。なんとなくふさわしいような気がしただけ。


2024年05月06日 (月) [PS5] 先週 Lords of the Fallen というゲームのダウンロード版を PS Store で購入したのだけど、キャラクリの最後で「有効な名前を選択してください」と表示され続けてそれ以上先に進めなくなっている。「プレイヤー名」みたいなプリセットが拒絶され、テキトーな日本語名が拒絶され、英数字のみの名前が拒絶され、空欄も拒絶された。ゲームが始められない。この状態が続くのであれば、俺は誰に返金を求め、誰に不当に預かられていた期間の金利を求めればいいのだ。■■■@2024-05-07 他にそういうバグ報告を見ないし、そのわりにゲーム開始不可はあまりに致命的な現象なのでちょっと考えてみた。月額課金は、していない人が無視できるほど少ないってことないだろうから、じゃあ、PSN へのログインかな。……というわけで、キャラクリの最後の瞬間にだけ PSN にログインしている状態である必要があったのでした。すんなりわかったわけじゃないよ。今日も、アップデートは……ないな、この名前だったら……ダメだな、というのをやっているうちに気がついたのだ。当たり前でないことを、「ほぼ」そうだからというだけで、勝手に当然の前提にするんじゃあないよ。どう見えるか、どうであるかではなく、どうあるべきかで行動するんだよ。漫然と現状をなぞる AI じゃねーだろ。


2024年05月04日 (土) [AtCoder] 今日は AtCoder Beginner Contest 352 があった。E 問題までの解ける問題を解きましたという感じ。たぶん精進として粘れば F 問題も解けなくはないのかなと思うけど、まずは WA がどういう思い違いから生じているのかを探る必要がある。ではふりかえり。■A 問題「AtCoder Line」。範囲に含まれるかどうか。大小関係が制約により規定されているわけではないことに注意。■B 問題「Typing」。B 問題にしてはいかつい制約(20 万)だけど、まあ、やります。T をスキャンしながら S の現在位置との比較を行う。■C 問題「Standing On The Shoulders」。肩の高さは常に答えに寄与する。頭の高さ、というか、肩より上に出る頭の高さは1つだけが答えに寄与する。合計足す最大値が答え。■D 問題「Permutation Subsequence」。結構考えました。そもそも記号混じりの問題文という時点で理解に時間がかかる。理解した内容はこう。K 幅の順列を考える。1から K とか、2 から K+1 とかの。次にそれらが入力 P の中でどの位置にあるかを考える。必要なのは最も左にあるものと最も右にあるものの差。これの最小値が答え。D 問題だからスマートな解法があるのかなと思いつつ、考える時間を惜しんで BIT でなぐりました。幅 K のウィンドウをスライドさせながら、入ってきた要素の位置を +1 し、出て行った要素の位置を -1 すると、値が1の位置(最も左にある要素の位置)と値が K の位置(最も右にある要素の位置)を BIT に尋ねることができる。■E 問題「Clique Connect」。愚直に辺を繋いでいくことはできない。扱う要素数は最大で 40 万だけど、その組み合わせは2乗の数になる。最初に最小全域木を求める操作をした。コストの短い辺から順番に、繋げられるけどまだ繋がっていない要素をどんどん繋げていった。これは組み合わせではなく、テキトーに選んだ1つの要素に他のすべての要素を繋げていく操作。それをコストの昇順に繰り返して最後に全体がひとつになっていればコストの合計を答え、そうでなければ -1 を答えにする。■■■@2024-05-05 F 問題の WA が減らないので代わりに D 問題を BIT で殴らない方法。提出 #53178338 (AC / 405 Byte / 123 ms)。配列を2本使うだけなので短いし、BIT だと付く log も付かない。スライド最小値とかいう名前がついてるのかな。初めて参加した AGC が AGC038 なんだけど、時間内に解けなかった B 問題 Sorting a Segment への他の人の提出を研究する過程で見つけたのがこれだった。ソースコードで学んだので名前は後付け。20190922p01。じゃあ昨日の ABC352-D が解ける人は青 diff だった AGC038-B も解けるね。■■■@2024-05-06 F 問題。ほんの2週間前に「重み付き UnionFind の実装は死ぬほどややこしい」と書きながら ABC314-F の精進をしていたのだけど、そのときは時間をかけながらも1発で AC になっていたのだけど、今回の問題は5つも6つも実装ミスが重なって5回も WA を出していた。すべては実装ミスだった。提出 #53204970 (AC / 1169 Byte / 174 ms)。後半にビットを埋める総当たり(メモ化あり)があるので N の制約が 16 以下と小さい。だから前半の UnionFind を定型から外れて雑に書いてバグり散らかしていたという話。制約が小さいならサイズやランクを見る必要ないもんね。そしてもちろん後半部分にバグがなかったというわけでもない。実装が下手。頭の中が散らかっている。■F 問題の解法について書いていなかった。パズルのピースを組み合わせるイメージ。相対的順位差で関係づけられたいくつかのグループがパズルのピース。3人の人が1つ飛ばしの順位だったなら、このピースは 10101 で表せる。全部で5人なら、これは 1-3-5 位しか考えられないが、全部で 10 人なら 2-4-6 位もありうる。しかし全部で 10 人いて他に 101011111 というピースがあるなら、やはり 1-3-5 位しか考えられない(※ MSB を1位としました)。こうなってくるととりあえず全部を試してみるしか答えを出す方法はないよね、という気持ちになる。1階層ごとに特定の1ピースを埋める DFS をやるんだけど、DFS の呼び出し経路には関心がない。どういうことか。1階層目と2階層目が受け持つピースがどちらも 11 という形だったとする。3階層目のピースにとって関心があるのは、そして結果に影響するのは、どのビットが空いているかということだけなので、同じ2ビットのどちらとどちらを1ピース目と2ピース目が埋めているかという情報には関心がない。むしろ積極的にその区別を捨てて結果の使い回しをしたい。メモ化です。というわけで、前半ではピースの形を確定する UnionFind を、後半ではピースを N ビットに隙間なく埋めるメモ化再帰をする2段構えの問題だった。要素技術は F 問題までで身につけているはずで、組み合わさったことで F 問題だったのかなと。G 問題ではないなと。ならこれも実装問題だったのか。突破できませんでした。腕力が足りない。■他所で見かけたのでここでも書くんだけど、置きにくいピース(※)から置くような小細工を WA だった最初の提出から行っている。これをやらなくても TLE にはならないだろうけど、とにかく答えを見つければいいという DFS の場合、優先順位を付けて早期に判定ができると劇的に実行時間が縮まることがあって気持ちがいいので、とりあえずやってみて沼にはまるのがいいと思う。その先にヒューリスティック沼があるかは知らない。この問題では、再帰を深く潜ってからやっぱりだめでしたーと判定されていたかもしれないものが、比較的浅い階層で無理だと判断できるようになる効果があったと思う。※置きにくいピースを短絡的にビット長の長いものとしたけど、ポップカウントもいい指標になりそうだと気がついた。10000011111 のどちらが置きにくいかという話。自分が明確に誤っていたのは、ビット長が同じでポップカウントが異なる 110001101111 の比較で、単純に数値比較で大きい方(最初の方)を置きにくいと判断したところ。小細工だしフレイバーなので許される手抜きだとは思うけど。


2024年04月27日 (土) [AtCoder] 今日は AtCoder Beginner Contest 351 があった。コンテスト成績証自分のすべての提出。ABCDF の5完でレートは横ばい。E 問題が難しかった。ではふりかえり。■A 問題「The bottom of the ninth」。問題文に書いてある通り、9回裏が必ずある。場合分けはいらない。1点上回るのに必要な点数。■B 問題「Spot the Difference」。唯一の相違点の座標を答える。座標が1始まりなんだよね。添字と1のずれがある。それで思いついてしまったんだけど、サイズを答えにすればずれの補正がいらない。つまり、後ろの方から一致している行を取り除いていくと、相違点のある行までが残る。残った行数がそのまま1始まりの座標になる。列番号も同様に末尾から削っていくと、残った文字数がそのまま1始まりの列番号になる。+1 とか -1 とかの補正って嫌いなんだよね。1か所も漏らさず完璧に補正できる気がしないし、+1 とか -1 とか見るたびにその意味を解釈させられるのが嫌だ。だから普段から無害な0番目を補うなどして補正の必要をなくすようにしている。メモリの方が自分の脳みそよりローコストだから、余分な1要素をケチる理由がない。入力を1回だけ補正して変換するという手もあるけど、そうすると実行結果とサンプルの解説文とでずれが生じるので避けたい。■C 問題「Merge the balls」。えっと、やるだけなの? 罠とかない? と警戒したけど、これは C 問題だった。では油断してそのままやります。2つの数を足す操作が +1 することを意味するというのがちょっとしたフックかな。掛け算が log の足し算になるみたいな。■D 問題「Grid and Magnet」。本日の実装枠でした。といって簡単というわけでもなかったと思う。基本は BFS、DFS もしくは UnionFind で連結成分の大きさを求めるんだけど、移動するとそこから動けなくなる吸い付きマスをどう扱うか。最初の UnionFind のステップでは吸い付きマスを壁として扱い、その後のステップで隣接している吸い付きマスと連結成分を一体として大きさを数えるようにした。気をつけたいのは、幅1の吸い付きマスが2つの連結成分を分断しているとき(そう、分断するんですよ。UnionFind をするときに吸い付きマスを使って連結してはいけない)、その吸い付きマスは両方の連結成分に対して寄与がある。どちらか一方だけに所属させてはいけない。前半のステップでも後半のステップでも吸い付きマスの特別扱いが罠になり得る。簡単ではないよ。■E 問題「Jump Distance Sum」。解けなかった。dist(Pi,Pj) がゼロになるのはチェス盤をイメージして2つの点が異なる色のマスにある場合。そうでない場合に dist(Pi,Pj) は X 座標の差と Y 座標の差のうち大きい方になるみたい。ある点を中心として座標空間を十字に区切ると、X 座標の差が dist になる範囲と Y 座標の差が dist になる範囲がそれぞれ2つずつ。BIT を2つ使って、ある座標までにある点の数の和と、座標の和を管理すれば、ある座標を中心として左右にある点との距離の和が効率良く求まる。ちなみに今日の F 問題がそういう問題だった。でも E 問題ではそれができなかった。点は左右だけではなく上下にもあり、X 座標と Y 座標のどちらか決まった方を dist の計算に用いなければいけないが、その分別が効率良くできない。■F 問題「Double Sum」。すごく安心感のあるおなじみの問題。もう5回も6回も書いてる気がする。BIT で値の和と値の数を管理する。Ai<=Aj を常に成り立たせるために昇順もしくは降順に A 数列を処理する。


2024年04月26日 (金) ふと思ったんだけど、骨を折る重傷で全治数か月とか、くっついた骨が十分な強度を持つまで3か月かかります(※期間はテキトー)みたいなのって、骨が作られて破壊されて置換されるサイクルがそれくらいってことなんかな。それをいろいろな表現で言い換えているだけ? 鈍い頭にそんな閃きが急に降ってきたが、例によって骨のサイクルを調べたりはしない。


2024年04月24日 (水) [AtCoder] 精進。「最近解けていなかった期待値の問題」3問から2問。■ABC314-E「Roulettes」。これもループを含む試行。「素直な問題設定」だった ABC350-E「Toward 0」と何が違うというのだろう。何も違わないと思う。でも難しく感じる。ABC350-E に提出した解答を想起しながらなぞるようにしてやっと解答が作れた。提出 #52759461 (AC)。■同じく ABC314 から F 問題「A Certain Game」。見え見えの UnionFind なのはわかる。でも UnionFind をしながら期待値をどこにくっつけるのが適切か、その期待値はどういう意味合いの数字か、よくわかりません。試合をした2チームを UnionFind で併合する。その後の期待値の増減は併合されたチームメンバー間で共通なので、根っこの期待値を代表にして操作する。ではメンバー個々が保持する値は何か。根っことの差分だけど、どこで根っことの差分が生じるか。勝ったチーム負けたチームと、大きいチーム小さいチームが必ずしも一致しないのがややこしいけど、小さいチームの期待値は試合後に大きいチームの期待値を基準にした差分として表現されるので、チームの併合操作の前後で小さいチームの期待値が変化しないように、大きいチームの期待値を打ち消すようにして差分が生じる。提出 #52760322 (AC)。……ということが理解できてから実装を始めたけど、重み付き UnionFind の実装は死ぬほどややこしい。E 問題の AC から1時間 20 分ほどかかっている。それはコンテストが始まってから終わるまでの時間とほぼ同じだ。■「最近解けていなかった期待値の問題」を具体的に特定してから書いたわけではなかったけど、あと心当たりがあるのは ARC174-C「Catastrophic Roulette」。この3問あたりが最近の解けなかった期待値の問題だったと思う。もう今日は ARC-C まで考える気力がない。■F 問題。Ruby によるすべての提出を眺めてると、どうやら、前半で UnionFind をしながら必要な情報を付加して、後半で逆向きにたどる構成の解答がほとんどみたい。うーん?