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

脳log[20250831]



2025年08月31日 (日) [AtCoder] 精進。昨日あった ABC421-F「Erase between X and Y」。クエリを先読みすれば前に詰めたり後ろにずらしたりしないでいいように適切な空間を空けて要素を挿入・削除できる気がした。1本の配列をどう切り分けて必要な情報をレイアウトしようか考えていたけど、結局必要なのは次の要素が何かという単リンクリストだけだった。クエリ2で問題になるのが x と y のどちらが前にあるのかという情報だけど、クエリ1のみを先読みして作った木構造を深さ優先でたどって通し番号を割り振って順序にできる……というのは各要素を決まった場所に並べて動かさないでいいという最初のアイデアと同じことを言っている。発想の順番としては木構造の方が先で、各ノードが要求する最大サイズが事前に先読みできるから、1本の配列上に木をレイアウトしたとしてそのあとノードを挿入したときに全体の再レイアウトが避けられるなと考えて、それからクエリ2によってノードが削除されたときに木構造を維持するために必要な更新をシミュレートしていたら(前から削除していくと子を残して親が先に消えるので子のつなぎ替えが必要)、木のような立体的な構造である必要はないな、ただの直線(リスト)だなと気がついて実装がとても簡単になった。■提出 #68964752 (TLE×2/AC×34)。なんでなんですか。一番に考えたのが 25 行目が無限ループになっているためにメモリを 1.5 GB ちかく消費し時間を 2.3 秒ほど費やしたところで実行を打ち切られているのではないかということ。しかし x と y の前後関係を見誤ってリストにループを作ってしまった場合、そのクエリの最中にリストを末端までたどった末に nil を演算に使用してランタイムエラーを出すはずなのだ。RE を出さずに無限ループをお膳立てする方法がわからなかった。それに他の AC になったケースをよく見ると、1.6 秒で約 1 GB のメモリを消費するなど、実行時間に比例してかなりのメモリを消費しているが異常ではないらしい。どこで多大なメモリを消費しているのかよくわかりませんが。■提出 #68969535 (AC / 924 ms / 352228 KiB)。2か所を while 文に書き直したらメモリ使用量が劇的に減って、したがってメモリ操作にかかる時間も減った結果 TLE が解消した。1つは .reverse_each.injectwhile .pop に、もう1つは .eachwhile .shift に。たぶん2つ目は時間に効くけどメモリに効くと意識したことはない。じゃあ Enumerator を返す使い方の Array#reverse_each に罠がありますか? あと、ちょっとだけ余分な時間がかかり、TLE と AC を分けることもあると知られている多重代入が複数の代入に分解されているのも確認できるけど、これは最初にこれだけをやってみて効果がなかったのでカウントしていない>提出 #68965976 (TLE×2)。■提出 #68970098 (MLE×2)。これは最初の TLE 提出の .reverse_each.inject.reverse.inject に書き換えただけのもの。reverse_each が罠なのかどうかを確かめたくて。結果は全体的に実行時間もメモリ使用量も何割か改善しているけど MLE になってしまった。そういえばメモリ制限は 1024 MiB だった。.reverse_each を .reverse に書き換えることで TLE は解消したけど MLE がそのままだったので AC にならなかった。ただの数値配列なのにメモリ制限が厳しい。使用済みのデータを随時破壊的にシュリンクしていって GC させないといけませんか。■Ruby の提出を時刻の早いものから見ていってる。提出 #68945143 (konayuki さん)。BIT を使って何をするのかわかりません。提出 #68947172 (hmmnrst さん)。なんとクエリ先読みが必要ない。クエリ2では x からと y からの両方を同時にたどってどちらが他方にたどり着くかを確かめている。自分の TLE/MLE の原因は x,y の順序を決めるための先読み部分の深さ優先探索にあったから、両方をたどってみるのが労なく AC できて正解だった。準備せずやってみるだけ! 同時にたどるのがたぶん重要なポイントで、順番にたどってみようとするとリストの全体をたどってしまうことを覚悟しないといけなくて、それは Q^2 のオーダーで TLE になる。他の提出は全部 BIT を使わないリスト解法かな。リストを二重リンクリストにして x から両方向にたどってるっぽいヴァリエイションもあった。■直近4回くらい緑パフォ安定で結果は出ないしコンテスト中は全然おもしろくないんだけど、だからふりかえりも書かないんだけど、精進日記や精進未満日記が3つ続くところを見ると問題自体に問題はなくて解くのを楽しめているような気がするんだよね(そのように見える判断できるというだけで楽しいと思ってはいない)。でも圧倒的に時間が足りていなくて、この F 問題は問題を読んだのが今日だった。この難易度の問題を後日提出の宿題にしてしまってはいけないんだよ。でも D 問題も F 問題も1時間では AC にならないし、E 問題は解けてもいない。メモ化再帰で書こうとして書けていない。キープする面の組み合わせをを選んでその場合の数を数えたいけどできない。■■■D 問題についても書こ。時間内に1時間かけたものは半分 AC で半分 WA だった (#68940840)。おしまい。終了後に最初の実装を捨ててもう一度書いた。実装の機微がすでに明らかなので、位置関係を分類して (左,右,同じ)×(上,下,同じ) の9通りを実装するのはやめて、現在の両者の距離と1秒後の両者の距離がゼロかどうかと変化量を見て判断することにした。分類して予測するより単位時間の経過を見る! 提出 #68953965 (AC)。D 関数がある時点の両者の距離で、X 関数がある時間の両者の重なり回数。実装し直しただけで AC になるのだから最初の提出の WA は実装ミスなんだろう。変化量が負になりうることを全く想定していなかったのでたぶんそのあたり。問題を一読したときから考える要素は1秒分もなかったけど、重なっている回数をうまく数えることができなかった。簡単な実装方針が見極められるのも実力のうち。ありませんでした。■■■リストを同時にたどるというアイデアが自分の頭から出てくる見込みがなさすぎて悔しいので、これが悔し紛れになるのかは知らないけど本で得ていた知識を1つ書こう。リストがループを持っているとき、その形は輪っかか輪っかにしっぽがついた「の」の字になるけど、輪っかがあるかどうか、あるとしたらそのサイズは何かを知るために2つのポインタでリストを同時にたどる方法がある(らしい)。アルゴリズムのキモはポインタの進む速さが異なっていることで、遅い方は1つずつ速い方は2つずつ進めていって、いつ2つが衝突するかを観察することから始めて、しっぽとサイクルの接続点を2つ(3つ)のポインタ以外の記憶領域を必要とせずに求めてしまう。そのときにはサイクルの長さもしっぽの長さも(数えたいなら)数え終わっている。というようなことが『プログラミング原論』の 2.3 衝突点 (pp.23-28) に書いてある。本の最初の最初のただの前置きみたいな部分なのに難しいね。