/ 最近 .rdf 追記 設定 本棚

脳log[2024-07-17~]



2024年07月17日 (水) 二三日は何日なのか問題。もちろんこれが問題になったことはない。自分としては、四六時中や四十九日、七十五日、三々五々から演繹するとニサンニチになるしかないと思うのだけど、実際にそうだったことがあるのを知っていることからもそう考えるのだけど、今 ATOK で明鏡国語辞典を引くと「しじゅうく‐にち【四十九日】〘名〙 人の死後四九日目に当たる日。また、その日に行う法要。七七日(しちしちにち)」とか書いてあって、これもうわかんねえな。いやホントはわかりますよ。「四十九日」「七七日」の2つと「四九日(目)」とでは使用時期にずれがある。辞典の言葉は収録される言葉よりも新しい。23 日を二三日と書くことの帰結として、10 月が一〇月になっていたのが先々週読んでいた小説。何を今更感しかないけども、そうなんだ、そりゃそうなるよな、だけど気持ち悪いな、その○はなんなんだ、漢字ではないよな、とひっかかってしまったのが今日の日記になっている。十月って書きたいよ。■ATOK で 10 を変換して出した一〇で使われているのは○ではなかった。U+3007 は「漢数字ゼロ IDEOGRAPHIC NUMBER ZERO」であり、U+25CB 「丸印,白丸 WHITE CIRCLE」とは異なる文字だった。〇が比較すると新しい字だけど数百年はさかのぼれるみたいなことが Wikipedia の漢数字のページに書いてある。お前漢数字なのか。漢数字とアラビア数字を区別して一方では位取り記数法を認めたくないっていうのは、単に好みや慣れや、旧弊に囚われているってだけなのか。自分はこれからも区別して用途を分けていきます。■用途を分ける(漢数字では位取り記数法を使わない)ことの別の一面は、「健康第1」とか「3位1体」とか「別の1面」とは書かないということ。算用数字を使うのは1、2、3と数を数える場面だけでいい。X と X+1 が可換な場合だけでいい。


2024年07月16日 (火) 子供の頃にスーパーボールで遊んだ記憶。今でも理解できないし説明できないのだけど、たとえば床に2バウンドさせて壁にぶつけるとする。1バウンドした位置から A の距離で2バウンドし、そこからさらに B の距離で壁にぶつかるとする。この A と B の距離(あるいは時間)の比率が跳ね返ったボールの跳ね方に反映されて、不等間隔の規則的な跳ね方を繰り返す。だったよね? おもちゃにも流行り廃りと技術や素材のアップデートがあって、今の子供はスーパーボールなんかでは遊ばないかなあと思ったので書いた。


2024年07月13日 (土) [AtCoder] 今日はトヨタ自動車プログラミングコンテスト2024#7(AtCoder Beginner Contest 362)があった。特に書くことはないけども参加記録として。自分のすべての提出。■A 問題「Buy a Pen」。3通りなのでどうとでも書きます。■B 問題「Right Triangle」。2乗のまま整数のまま三平方の定理で判定します。■C 問題「Sum = 0」。最初はすべて L に寄せて和の最低値を求めます。あとは和が負である限り、各組 R-L を上限として右に寄せていきます。■D 問題「Shortest Path 3」。辺だけでなく頂点にも重みがあって何かが変わるかなと考えたけど、普通に普通のダイクストラ法のままだった。■E 問題「Count Arithmetic Subsequences」。N の制約が 80 以下と非常に小さい。これだけ小さいと O(N) と O(logN) の差よりも定数倍の違いの方が大きくなりがち。80 の3乗が大した大きさでないことを確認して愚直3重ループを書いたら TLE が出てびっくりした。3重ループと書いたけど実は3つ目のループは再帰関数の中に隠れている。それって3重にとどまらないのでは? やばいのは公差が0のケースなのでそれだけ別で数えて AC。■F 問題「Perfect Matching on a Tree」。マッチングとかフローとか辺被覆とか点被覆とかはわかりません。「最大重み最大マッチング」てなんだったんでしょうか。まんまと「知らないキーワード、怖い」と G 問題に流れていっちゃったけど、この問題は難しいことは考えずに、木の中心で頂点集合を2つに割って組み合わせたらどうかなと思った。WA だった。この問題の何が問題なのかがわかっていないか、うまくできなかったか、どちらなのかもわかりません。とはいえ、うまくできていなかったのは間違いない。中心に頂点があるとして、中心から3本以上の辺が出ている場合が考慮できていなかった。■G 問題「Count Substring Query」。ミシシッピの s が足りないのが気になってしょうがない。なんでコンテスト中に辞書を引いてるんだ……。F 問題より見込みがあるかと先に考えていたけど、数を数えようとするとどうしても2乗の時間がかかりそうになる。接尾辞配列を使うだけみたいなツイート(?)を読んだ。その使うだけもできないのだから惜しくも悔しくもないけども、接尾辞配列とか完備辞書とか Trie とか、見て見ぬ振りをして実装せずに来たツケなのは間違いない。「SA-IS 法のメモ - まめめも」8割くらい読んだ。しれっと挟まれた翻訳者自らの宣伝だけど、その本はもう持っている。悲しいかな理解が追いつかなくて積まれているけども。SA-IS 法に話を戻すと、2割が完遂できなくて写経することしかできない。たとえ写経でもするべきなのかなあ(やらないつもり)。やると書いて、やる、有言実行メソッドと、やらないと書いて、やる、天邪鬼メソッドの両方があります。自分のモチベーションをより高める方を選んでどうぞ。■■■精進。F 問題。提出 #55644781 (AC / 1365 Byte / 636 ms)。やりたいことのイメージは合ってたみたい。中心で2つかそれ以上の頂点集合に分けて、異なる集合から1つずつ選んでペアを作る。それをどうやってうまくやるか。葉から処理を始めて、親にどんどん頂点集合を集約していく。その集合の大きさが過半数になってはいけない。最終状態がいくつか考えられる。わかりやすいのが、集約された結果ある頂点とその2つの子が残り、そのサイズとつながりが (N/2)-(1)-(N/2) である場合。左右の子から1つずつ頂点を取り出してペアを作ればいい。総頂点数の偶奇により中央に頂点がない (N/2)-(N/2) のパターンも考えられるがやることは同じ。もうちょっとわかりにくいのは、ある頂点が3つ以上の子を持っていて、どの子に軸足を移そうとしてもその瞬間に他の子が過半数の大きさの頂点集合を作ってしまう場合。最も単純なケースは、2から N のすべての頂点が頂点1につながるスター型のグラフ。頂点1を中央に据えて、他のどの子の頂点集合もサイズはたったの1だけど、どの2頂点もマージできない。そんな感じで頂点集合をマージできるだけマージして残った頂点集合の集合からペアを作るのが次のステップ。同じ集合の中からペアを選びさえしなければいい。だからプライオリティキューを使ってサイズの大きい頂点集合から優先して2つ選んでペアを作ることを繰り返した。もっと洗練されたうまいやり方と考え方が絶対にありそうだけど、AC なんやしまあええやろ(向上心のない怠惰な態度)。あと、自分が中心と呼んでいるものに重心という名前がついているらしいのは何か所かで読んで把握している。■■■F 問題。某週記を読みまして DFS 順でペアが作れると知った。DFS の行きがけ順(に限らない?)に 27 個ないし 26 個の頂点を A-M$N-Z と並べたなら、(A,N),(B,O),(C,P),...,(M,Z) のペアを作る(のだと思う)。ローカリティに注目すればなんとなくそれでいいような気がするし、逆に両端からペアを作っていくのがダメなのもわかる気がするが、いつでも絶対それで OK とはわからない。■自分の解答の後半のステップはプライオリティキューを使わないで、単純に入れ子配列をフラットにして真ん中で切ってペアを作るので良さそう(C.flatten!; C[C.size/2..].zip(C))。どの解説もそういうペアの作り方をしてるので。どの頂点集合も過半数に届かないようにしているのだから、たしかにそれで同一集合からペアを作ることはないみたい。それと、DFS での頂点の並べ方は帰りがけ順でも OK と解説に書いてあった。


2024年07月12日 (金) PS5 のコントローラーって臭くなりやすいと思う。手に汗握る状況が多いというのが根本にあるとしても、PS4 まではコントローラーが臭いとかコントローラーを握っていた手が臭いとかが気になったことはなかった。ウェットティッシュでよく拭いてリセットしても数日で臭くなる。材質かな。表面処理かな。何が違う?


2024年07月08日 (月) 先日自転車のハンドルを掴み損ねて上半身から地面にダイブした。雨の日で手袋をしていたので手は平気。手袋も無事。ズボンの膝がちょっとほつれてるけど穴は空いていない。一番のダメージが肋骨にある。指で押さえると痛む場所が1か所特定できるだけなのだけど、肋骨の痛みによる QOL の低下が著しい。咳をすると痛む。深呼吸をすると痛む。布団でうつぶせになると痛む。日常の、ささいな、何かしらの行動に対するリアクションとして痛みが発生することのなんと不自由でうっとうしいことか。年を取るとたんが絡むんですよ。若い子みたいにのどの奧がつるっとしてないんで。咳払いでいっときの疎通を確保しようとするたび肋骨が痛む。つら。■「雨の日で手袋をしていたので」と書いたことについて。まぎれもない事実なのだけど、雨の日でないなら手袋をしていないという裏読みをさせてしまうとしたらミスリーディングである。ある命題の真偽と裏の真偽に関連はないのだとしても。誤解がないように書くと、雨の日だから雨用の手袋をしていた(晴れの日は晴れ用の手袋をしている)。どちらも手のひらは革で丈夫。もし「手のひら以外は革でない」という裏読みをしたなら、半分間違っている。


2024年07月06日 (土) [AtCoder] 今日はデンソークリエイトプログラミングコンテスト2024(AtCoder Beginner Contest 361)があった。自分のすべての提出。■A 問題「Insert」。問題名の通りに Array#insert を呼び出す。いつまでも引数の順序が覚えられないメソッド。今日はリファレンスを引くのも面倒がってテキトーに位置と値を並べて呼び出して結果を確かめた。■B 問題「Intesection of Cuboids」。現在は修正されているタイトル名のなごりが見える(イン「テ」セクション)。コンテスト中に気が付かなかったのは不覚というほかない。誤字脱字はかなり神経質に駆逐したいタイプなので。さて本題。頭で立体交差を想像するのは難しいけども、これは1次元の交差を3回確かめるだけ。制約を確かめてから、ソートしたりイコールの場合を考慮したりしたけども、今改めて制約を見るとちゃんと不等号で大小関係が規定されていた。目がついていない。言い訳をすると、同じ範囲の制約がたくさん並んでいたから、すべての入力値が範囲内で無制限の値を取るのだと思ったのだった。6つも 12 個も同じようにたくさんでしょうよ。目がついていないのと数が数えられないのと、どちらがましか、言い訳になっているか。■C 問題「Make Them Narrow」。ソートして最小値を決め打つ。Array#each_cons がおそらく線形時間を要するせいで TLE を1回出した。たしか以前も同じメソッドで TLE を出している。学習しないな。だけども、配列の dup や shift、pop では配列の要素レベルのコピーが抑制されると知っているから、配列から固定サイズの部分配列を切り出すのに単位時間しかかからないと期待するのは当然じゃない? each_cons が Array#each に基づいて実装されてるとしても、一度の each_cons 呼び出し(と複数回のブロック実行)で線形時間しかかからないなら TLE にはならなかった。尺取りをするなら TLE にはならない。ブロック実行のたびに線形時間かかっていて、each_cons 全体では2乗の時間がかかっているからこそ TLE になる。これが予想外。■D 問題「Go Stone Puzzle」。制約がごく小さいのでメモして BFS をする。実装でいろいろミスをした。たとえば (1,2)、(3,4)、(5,6) みたいな (奇数番目,偶数番目) のペア単位でしか交換できないと思い込んでいたり。そこはちゃんとサンプルで落とされたけども、見るべき要素数が N ではなく N+2 だったりも。15 分かかった。■E 問題「Tree and Hamilton Path 2」。ARC179-D Portable Gate の簡単バージョン。精進記録はこちら>20240602。根を決めて DFS ですべての頂点を訪れてまた根に戻ることを考えると、すべての辺をきっちり2度ずつ通るのでコストの上限がまず決まる。本問題では根に戻る必要がないのでどれだけのコストが節約できるか。直径。自分の提出がすでに求まっている直径を再帰関数で改めて求めているのは、紆余曲折の結果なのです。引き算で求めるのではなく、2種類の再帰関数で積算して答えを求めようと迷走していたなごり。プライオリティキューを貼ったけど、いらなかったらしい。根からすべての頂点への距離を求めているのだし、各頂点へのパスは1つだけなのだから、それはそう。■F 問題「x = a^b」。苦手な苦手な数学問題。指数を3以上に決め打つなら N 以下の全ての立方数、4乗数、5乗数(って呼ぶのか知らないけども)……を列挙して重複を排除できる。じゃああとは最大で 10^9 個ある平方数をどうやって数えるかだけ。すでに数えた立方数、4乗数……が平方数でもあるかどうかを確かめて重複を除外することでクリアした。この問題の提出でも自分は変なことをしている。b を固定したときの a の最大値を Math.log で求めてからすべての a^b を列挙してるんだけど、それだったら a=1 から始めて N<a^b になるまで a+=1 を繰り返せばいい。コンテスト中はそこまで頭が回らないんだなあ。■G 問題「Go Territory」。解けてないよ。サンプルにあるように囲われている格子点を数えたい。縦横斜めに並んだ石のあいだで UnionFind をして、まずは輪っかを作っているかもしれないグループを特定した。次にグループを構成する石の周囲8マスから BFS を開始して孤立している格子点の島を数えようとした。どこで探索を打ち切るか。グループを構成する石の X 座標 Y 座標の最大最小を限度にして BFS を打ち切って孤立していないと判断をした。これでサンプルは合ったけど、WA があるかもそうだけど TLE は間違いないと思うんだよなあ。おおざっぱに2乗の範囲を塗りつぶそうとしてるわけだから。■コンテスト成績証。黄パフォに限りなく近いいいパフォが出たけども、前回(ABC が Unrated 判定だったので配点と Writer から必敗を覚悟して出た ARC)の緑パフォの傷が癒せただけなんだよなあ。F 問題まで4桁人数が解いてるので早解き回だったみたい。それは配点からも読み取れる。簡単な問題に滅法強いという自己評価を裏切らない結果。■G 問題。自分のやり方で WA になる例。ループが2つ左右にくっついていて、右のループの周辺ということで、右のループの外側、左のループから見れば内側から BFS を開始した場合、打ち切るタイミングを誤る。盤面を BFS のたびにクリアすれば解決するけど、ますますもって TLE が加速する。■■■精進。G 問題。提出 #55351016 (AC / 796 Byte / 571 ms)。kotatsugame さんの動画(【競技プログラミング】ABC361【実況】)で解法を聞いたんですよ。UnionFind をするのは石と石ではなく、縦もしくは横方向に連続した格子点列の隣り合ったもの同士だった。やってることは難しくなくて、付加情報として格子点数を持った UnionFind だけど、頂点番号が与えられているわけではないので、格子点列に自分で番号を割り振らないといけないひと手間がやっかい。Y 座標列をソートし忘れたり、ひと手間に色んなミスがからんでくる。グループの管理と格子点数の管理を同時にやろうとしてグループ0と数0を混同したりもした。また、格子点の総数が数えられなくて答えがなかなか合わなかった。X 座標の最大値が 200000 だとして、0 から 200000 のあいだには 200001 個の格子点があるんですよ(あたりまえ……ではなかったんだなあ)。


2024年07月01日 (月) [AtCoder] 精進。昨日あった ABC360-E「Random Swaps of Balls」。ひとえに、ひとえにデバッグ力が足りなかった。サンプルの2に 3 2 という入力例がある。サンプル1のように解説がないし、出力例が実数ではなく 554580198 (mod 998244353) だということで、途方に暮れていたのが昨日。だけどね、自分で計算すればいいんですよ。昨日の方針だけど、場合の数を K 回数え上げていって、最後に N^{2K} で割ることで確率に変換するようにしていた。ということは、3 2 という入力に対しては9通りの出目が2つ続く 81 通りの場合がうまく数えられていれば良かった。81 に足りなければ遷移の式が誤っているということ。そのようにして今日はサンプルの2が合った。そして次のサンプル3の入力が 4 4。これの答えがまた合わなかったのだけど、同じようにして 4 2 に対する 256 通りの数え漏らしを見つけることでサンプルの3もまた合った。■提出 #55117837 (AC / 362 Byte / 126 ms)。N=1 で WA を出さなかったのはすでに他所で知っていたから。全く警戒していなかった。■この E 問題までささっと解けてまだ不満というのが自分の現状認識なんだけど、また脳みそにクモの巣が張ってきてるみたい。半年後からの挽回に期待!■続けて G 問題 Suitable Edit for LIS にも取り組んで提出したのだけど、#55121924 (WA×8/AC×45)、WA になるケースが全く作れなくてデバッグが進まない。C++ の AC 提出と答えを突き合わせたりしてるんだけど、最初にコピーしてきた提出は長さ6程度のシャッフルした順列に対する答えがおかしかったよ(それでも AC)。2つめにコピーしてきた C++ の AC 提出とは意見の一致を見たので、自分だけが盛大な勘違いをしているということはなさそう。こんなザルなジャッジだったら8個くらいの WA は見逃してくれてもええやろ。■G 問題について現在わかっていること(あるいは勘違いしていること)。操作によってできるのは LIS の長さを +1 することだけ。先頭か末尾の値を含まないで LIS が作れるなら、先頭か末尾の値を操作して +1 できる。では先頭と末尾の値が LIS に不可欠だったら? 適切な位置に LIS に参加しない空き要素があるなら +1 できる。適切とは? 空き要素があってもその前後にある LIS を構成する要素が連番だったら +1 できない。条件はこれだけだと思う。ところで LIS って候補がいくつもあるのが普通。最長の長さを知りたいだけならそれらを区別する必要がないけど、今回は空き要素を挟めるか挟めないかがポイントなので、LIS の個別の列に踏み込んでいかないといけない。添字列に対して LIS を求める操作をして作業配列を上書き更新せずに追記して履歴を残すようにしたらうまくできないかなーと思って書いたのがさっきの提出。なにがうまくないのかわかんない。思いつきでいけるやろって思う方がおかしいか。そうか。■■■通ったー!!! G 問題。提出 #55154669 (AC / 672 Byte / 277 ms)。前の提出が 671 Byte だった。17 行目の <<= にすると WA が AC になった。狭義単調増加だから、値の比較にイコールがないのは一見自然なんだ。でも 17 行目では LIS に含まれない要素をはじいていた。< の否定が >= であるように、そこではイコールが必要だった。なんだー、1文字かー。ランダムケースの生成ルールを A = 1,*([2,3,4]*3).shuffle,5; puts A.size,A*' ' に変えたらすぐに見つかった。同値の要素の重複がキーだった。ともあれ、やろうとしたことがちゃんと AC に繋がっていたのは良かった。■LIS の履歴の活用法について書く。作業配列には通常長さ i の増加列の末尾の値を記録する。最終的に i が取り得る最大値が LIS の長さ。今回は値の位置も知りたいので値の代わりに添字を記録し、さらに履歴も知りたいので各長さ i それぞれに添字列を記録していった。入れ子配列の総要素数は N+(LISの長さ) になる。作業配列には最長に届かなかった短いものも含む全ての長さの増加列の情報が記録されている。これを後ろから見ていくことで LIS の一部ではない要素をはじいていくことができる。値を見ることで添字列の先頭から、添字を見ることで添字列の末尾から取り除いていくことができる。たぶんね。あとは前後の添字列を見比べて LIS に中間値を挿入する添字の空きと値の空きがあるかを確かめる。■■■遅れていたレート更新があったのだけど、B 問題で範囲が間違っていた普通の WA コードを投げた人が Unrated に不満を表明していた。テストケースの制約違反とは関係なく WA だった人も Unrated なの? 確かめたら自分も Unrated で草。4完低パフォだから不満はありませんけども。1≦c≦w という範囲を添字にするときに、c を 0 始まりにするのを忘れて各行1文字目を縦読みするケースが漏れていて WA を1回出したんだよね。


2024年06月26日 (水) 競プロ出身者の使えなさは異常」■すごくありそうだなって思う。全体から見れば一部だし界隈によらず存在する特性でもあるはずだけど、特に優秀で他によりどころがない場合に、ああいう悪い感じに増長してしまうことはあるだろうなと思う。競技プログラミングになじむ時点でその素養は高いかもしれない。もちろん主語がでかいし偏見だし個人を見ろと言いたいし、クソと言っている周囲がクソな環境にずぶずぶにつかったクソである可能性も常に考えている。増長っていうか防衛にも見える、ハリネズミ的な。誰も認めないから主張が先鋭化するんじゃないのか。認めつつ穏当に導くことも、マウントを取り返して黙らせることもできなかった結果ではないのか。なんにせよミスマッチはその通り。お互いに不幸でしたね。


2024年06月25日 (火)

最終更新: 2024-07-12T00:35+0900

[AtCoder] JOI Open Contest 2024 過去問A - 試験 2 (Examination 2)

いつもの前置き:提出先は AtCoder だけど問題は AtCoder の作成ではなく JOI で、だけど競プロカテゴリを意図して AtCoder タグを付けています。

しんどかった。BC 問題を読むのは気が向いたときにして、A 問題についてだけ書く。

 どういう問題?

4種類の論理演算(! & ^ |)と、かっこと、ある値を境にして真偽が切り替わる関数から成る式の値について、クエリに答える問題。式の長さが 1M 文字以下で、クエリとして与えられる値の数が 200K 個以下。つまりどちらもとてもでかい。

 アイデア1:クエリの並び替え

クエリを昇順に並び替えたら、入力値との比較で真偽が決まる関数というのは、一度だけ false から true に値が切り替わる関数になる。更新頻度が (関数の数)×(クエリの数) から (関数の数) に減る。

 アイデア2:セグメント木みたいに関数の値の変化の影響範囲が対数レベルにならへんの?(願望)

以上の2つのアイデアというか願望に基づいて最初に書いたのが次の提出

 提出 #54709820 (RE/TLE/AC)

関数を class で置き換えて、そのクラスに演算子を定義して、式全体の評価を eval で行うお手軽な実装。

 RE の原因は Ruby が非ゼロの終了コードを返すこと

eval であるか *.rb ファイルを実行しているかに関係なく、大きな式を評価しようとすると Ruby は黙って異常終了する。式の形によっては終了する前に nesting too deep (SyntaxError) というエラーが出ることもある。p 1^1^1^1^1^...^1 という 1 か 0 を表示するスクリプトをコピペで膨らませて実行すると、Ruby のバージョンと Windows でのスタックサイズに左右されるんだろうけど、手元では 7 KiB を超える前に何も出力されなくなった。

 TLE の原因は、願望が現実ではなかったということ

両極端の例を挙げると、1+2+3+4+5+6+7 という式を [+ [+ [+ [+ [+ [+ 1 2] 3] 4] 5] 6] 7] と解釈するとき、(1+(2+(3+(4+(5+(6+7)))))) という式を [+ 1 [+ 2 [+ 3 [+ 4 [+ 5 [+ 6 7]]]]]] と解釈するとき、階層が直線的に増えていく。

 対策1:逆ポーランド記法

中置記法とかっこの存在が式の扱いを難しくしている。

変換アルゴリズムがうまく書けなかったので、検索してトップに出てきた「逆ポーランド記法入門2|中置記法から逆ポーランド記法への変換」というページを参考にした。

うまく書けなかった部分、求めていた答えがまさに「演算子を全てpopして」と与えられていて喜んだのだけど、あとになって答えが合わない原因が「全て」にあることがわかった。上のページで扱っている四則演算のように、演算子の優先順位が1位と2位の2つだけなら「全て」で正しい。それより多いなら、「これから入れようとする演算子よりも優先順位が高い演算子を pop できる限り全て」が正しい。日本語の説明と Python の実装例に齟齬はなかったので、日本語だけの問題ではないみたい。その一部分を除けば文章だけでアルゴリズムの全体像がイメージできるわかりやすい説明ではあった。

これで RE は解消するけど TLE はなくならない。むしろ eval を呼び出してインタープリタに丸投げすることができないぶん遅くなる。

 対策2(失敗):更新の伝播を下の階層から、更新を階層ごとにまとめて

例えば [1]^[2]^[3]^[4]^[5] という1つの式があって、クエリが 10 だったとする。最初は全ての関数が false で、クエリの昇順に true に切り替えていく。10 が最小のクエリなら、1 から 10 までの関数を順番に true に切り替えていくのだけど、その過程でこの式は false>true>false>true>false>true と値をチカチカ切り替えていく。もしこの式の全体が別の式の一部分であるなら、それら外側の式も同時に連鎖的に値を切り替えていくことになる。

更新の伝播を階層ごとに一旦ためておいて、深い階層から足並みを揃えて各項につき1度だけ値を更新しようとした。その過程で更新と更新が重なって更新が消えることも期待できる。ただまあ、これは根本的な解決策ではなく、効果がある入力もあるかもね、というレベルの対策にすぎなかった。TLE はなくならなかった。

 対策3(失敗):式を真ん中で半分に

演算子の優先順位を考慮して、| ^ & の順番で、もっとも真ん中にある演算子で分割する。

たとえば [1]&[2]&[3]&[4]&[5]&[6] という式なら [& ([1]&[2]&[3]) ([4]&[5]&[6])] と分割する。

たとえば [1]&[2]&[3]&[4]|[5]|[6] という式なら [| ([1]&[2]&[3]&[4]) ([5]|[6])] と分割する。

ところでこの分割は同一階層内でしかできないので、[1]&([2]&[3]&[4]&[5]&[6]) の分割結果は [& ([1]) ([2]&[3]&[4]&[5]&[6])] になる。かっこを無視して分割してしまうとあとでどうくっつけていいかわからないので。

かっこに制約されるということで、更新が影響する範囲を対数レベルに抑えて TLE を解消する助けにはならなかった。

 対策4(失敗):逆ポーランド記法の式を操作して同一演算をマージする

なぜか抵抗があって考えないようにしていたのだけど、TLE 続きでなりふりかまっていられなくなったので、結合法則を使う。実は対策3でももう使っている。

「なぜか」っていうか、左結合だと問題に明記されていたから、それを無視するのはずるであると、理性ではなく感情が訴えるのだ。答えが合いさえすれば何をしてもいいという教育は受けていない。たぶんそういう理由。問題の解釈が曖昧にならないように出題者が左結合だと定義するのはわかる。交換して答えが変わらないのを見抜いて利用するのが解答者だというのもわかる。だけど無意識が抵抗している。まあいいや。

中置記法の式はかっこがあってややこしいので逆ポーランド記法の式を操作する。

たとえば 1+2+3+4+5+6 を逆ポーランド記法に書き換えると、1 2 + 3 + 4 + 5 + 6 + になるのだけど、演算順序をいじって 1 2 3 4 5 6 + + + + + に書き換えたい。

そうすると何が嬉しいか。演算子に由来する階層を取り去って [+ ([1]) ([2]) ([3]) ([4]) ([5]) ([6])] と解釈することが容易になる。次にこれを6項演算として扱うこともできるし、2項演算のまま引数列を半分半分にしていくこともできる、半分半分にするということは、セグメント木のようにきれいで効率的な階層が作れるということ。

正しい方向に向かっていることを予感させるけど、1パスでやろうとして十分にマージできなかった。

たとえば次の式の書き換えを考える。1 2 + 3 + 4 5 6 + + +。参照範囲が限定できるので予め連続する演算子を結合しておくと便利。1 2 + 3 + 4 5 6 +++ になった。左から読んでいって最初の + 演算子に出合うとき、右の要素が被演算子(3)でその次の要素が + 演算子なら、順番を入れ替えて 1 2 3 ++ 4 5 6 +++ にできる。しかしこれ以上の結合ができない。被演算子もまとめておくとひとつ飛ばすだけで +++ にアクセスできる? だけど 4 という項は 2 2 * という演算子を含む3項として存在していることもありますね。

 対策5:逆ポーランド記法の式を解釈するときに同一演算をマージする

対策4とやりたいことは同じなんだけど、今度はうまくできた。演算子に行き当たったとき、スタックから取り出した2つの値(a, b)を即座に [op a b] としてスタックに戻すのではなく、ab が単独の値か何らかの演算の結果なのかを調べて可能ならマージして、できるだけ階層を増やさないようにした。そしてそれが最後でもなく、スタックから取り出されてまたマージされることもある。

 提出 #54933176 (AC / 2299 Byte / 1088 ms)

これの 92 行目から 107 行目のあたり。

2K オーバーの長大なスクリプトになってしまった。絶対もっと短く賢く書けるはずでしょ。

 ざっと読んだ


2024年06月24日 (月) セルフレジであたふたしてる年配者を見るにあの手の人々は「文字を読まない」ので文字情報は彼らには無力 - Togetter [トゥギャッター]」■何度も利用しているスーパーのセルフレジに言いたいことがあります。セルフレジに2種類あるのね。それぞれに、「こういうキャッシュレス決済が使えます」「キャッシュレス専用です。現金使えません」とか書いてある。一度間違えたからその次の機会に、どこに注意していれば間違えずに済んだかという観点で注意を払って観察してみたことがある。全然わからなかった。2種類のセルフレジは大きく左右のゾーンに分かれていて、待機場所も左右に隣り合った位置が別々に指定されている。レジと列に違いがあると知っていて、どちらに立つべきか注意して観察して、それで全然わからなかった。足下を見ても、左右に視線をやってレジまわりのポップを眺めても、判別できなかった。あとになって判明した理由は2つ。1つは、どこにも「現金が使えます」とは書かれていなかった。「現金が使えない」ことを知りたいんじゃないんだよ、しかもそれをレジ前で知っても遅いんだよ(これが2つ目の理由)。そして現金が使えないレジを見つけることは、現金が使えるレジを見つけることを助けはしない。現金が使えるかどうかわからないレジが存在しているだけなんだ。現金が使えないレジが現に存在しているのだから、これは自然に生じる疑いだと思いますがどうですか。あたふたする原因が導線の悪さにある例でした。スキャンを全部終わらせたあとで支払い方法の選択肢に現金がなかったんだよね(サポート店員さんがレシートを発行して現金可のレジに読み取らせることになった)。レジを区別するという注意コストを不特定の客に要求しているのだからこれはもうどうしようもない。ハードルがあるから越えられない人が出る。なくせないならせめて低くする仕組み作りを続けるしかない。


2024年06月22日 (土) [AtCoder] 今日はユニークビジョンプログラミングコンテスト2024 夏(AtCoder Beginner Contest 359)があった。F 問題まで解けたけどなぜか D 問題が解けない謎の回。ではふりかえり。■A 問題「Count Takahashi」。普通に Takahashi を探したけど、制約を読めば T を数えるだけでも良かったらしい。他の人の提出で知った。そうか、手の抜き方を見習わないといけないな。無駄にコンピューティングパワーを使ってしまった。■B 問題「Couples」。正規表現の先読みでやったけど、結構複雑で罠もあるパターンになったので(/\b(\d+)(?= \d+ \1\b)/)、普通に Array.each_cons(3).count{...} でやるのが良かった。■C 問題「Tile Distance 2」。数え方が ABC353-F Tile Distance と同じ。斜めを数えてから縦と横を数える。横移動のコスト計算が難しかった。目的地がタイルの右側か左側か判別してから、右と左のどちらから移動してくるのか場合分けをした。■D 問題「Avoid K Palindrome」。ABCDEF で一番難しかった。えっとね、まず否定で良い文字列を定義するのをやめてほしい(わがまま)。かなり長いあいだ勘違いしていた。何を数えたらいいのかまだわかっていない。長さ K の枠の中について回文があるとかないとかを数えるとするじゃない、でも枠の外を数えることができない。重複を省いてどうやって数えられるのか。■E 問題「Water Tank」。特別なデータ構造はいらない。スタックが1つと数を1つ覚えておくだけで答えが出せる。こういう単純な道具をこねこねする問題は好きだけど、前回の D 問題が灰 diff だったことを考えると、緑 diff どころか茶 diff があるかどうかもわからないと思う。高めに出てもそれは D 問題の壁があったからだよ。■F 問題「Tree Degree Optimization」。Ruby での他の人の提出を見ると、自分のよりずいぶんとシンプルで、自分には見えていない何かがあるのかなと思わされる。自分が考えたこと。各頂点が単独でばらばらに存在していて、(相手のいない)辺を1本伸ばしている。コストはその頂点の値(A)と現在の次数による。木を作るのだから、N-1 回、2本の辺を選んで繋ぐことを繰り返す。繋いだときにコストが発生して、それぞれの頂点からはまた新しい辺を生やす。辺を選ぶのにプライオリティキューを使ったけど、同じ連結成分から生える2本の辺を繋ぐことはできないので、単純にキューから2つ取り出して繋ぐというわけにはいかない。自分はキューを2本使った。1本目のキューにすべての辺を入れて、2本目のキューには最小コストの辺と、それと同じ連結成分の辺を入れた。そうすると2本のキューの最小同士を連結することでうまく処理が進む。だけど他の人の提出を見ると、キューは1本で足りるみたいなんだよね。どう考えるとそうできるのかわからない。■F 問題。次数の和が 2(N-1) になることを利用するのかな。キューには常に N 個の数を入れておいて、2つ取り出してコストを計算して、2つ戻す。自分の提出が無駄に複雑になったのは、個々の頂点の繋ぎ方を区別していたからだ。次数だけをカウントして繋ぎ方は考えないでおくと、シンプルになる。たぶんね。ほらね。提出 #54856675 (AC / 753 Byte / 562 ms)。ほらねじゃない。2つ取り出すのは嘘だった。1つずつ N-2 回取り出す。次数の和が 2(N-1) になるというだけでは条件が足りない気がした。各頂点の次数が最低でも1あるというのも条件なのかな。だから次数 N は予約済みとして、残りの N-2 をキューから引いた。■F 問題。tinsep19 さんのこの提出 #54855773 は個々の辺の繋ぎ方を考えつつもシンプル。木の問題の制約で自分が好きな形式があって、こういう形で親を定めるもの p2,p3,p4,...,pN (1≦pi<i)。これでどんな形の木も作れるんだよ。これと同じ考え方で処理を進めていてシンプルになっている。自分の考えが1つも2つも足りないのが明らかになっていくなあ。■F 問題。自分の最初の AC 提出には嘘があると思う。辺を繋いで、新しい辺をキューに追加するときに、2つ目のキューが空かどうかを確かめて適切なキューに追加するようにしないと、取り出し済みのコスト最小の辺と、2本目のキューに入っている同じ連結成分の2番目以降の辺の、コストの大小が入れ替わってしまうおそれがある。これは自分がキューのトップを参照するのではなく、とりあえず取り出してみた最小値を変数に保存して利用しているせいで生じているバグ。あぶないところだったなあ。■■■G 問題「Sum of Tree Distance」。全方位木 DP だと思ったら普通の木 DP だった。木 DP は DP の中でもやりやすい印象を持っている。積み上げの基礎となる葉の部分が最も単純でとっかかりとして好都合だからだと思う。その代わり子のマージに神経と時間を使う。提出 #54881518 (TLE×2 / AC×50)。2ケースダメでした。何が弱点だろう。メモリ消費と時間がだいたい比例している。メモリ消費は Hash かなと思うけど、どういう形の木だと無駄が増えるんだろう。単純に頂点数じゃあないんだよな。■精進。D 問題。X で ABC305-G Banned Substrings の簡易版だと読んだので、自分の提出 #42262239 をなんとか解読してそれっぽいものを書いた。提出 #54884526 (AC / 496 Byte / 147 ms)。おもしろいのは、っていうかやられたなって思うのは、入力文字列中の ? の出番がごく控えめなこと。力技で長さ N の良い文字列を構築していくにあたって、入力文字列中の AB はふるいとしての役割を果たしているのだけど、? というのは A でも B でもいいという意味だから、ふるいとして働かないことが機能なのだ。注目する部分が間違っていたし、プログラムの構成も全く予想していないものだった。■■■G 問題。朝のトイレでひらめいてしまった。マージテクのためにサイズでソートしてるんだけど、そのサイズって5とか3の固定だったりしませんか。帰ってからデバッグする。提出 #54896069 (AC / 839 Byte / 3169 ms)。通りました。まさかのマージテク失敗が TLE の原因だった。あれこれ考えることが多いと手癖にまかせて .sort_by(&:size) とか書いちゃうんだな。ところで、今確認したところ tinsep19 さんが G 問題を 528 ms で通している。同じ Ruby なのに桁が違うんだけど、何をどうやっているのか、知りたいが読んで知りたくはないんだな。■F 問題。「自分の最初の AC 提出 #54840376 には嘘があると思う」と書いた。実際に確認したところ、新しい辺を無条件に2番目のキューに入れていたので、懸念していたコストの逆転は起こらないはずだけど、代わりに「2つ目のキューが空かどうかを確かめて適切なキューに追加する」ことができていなかったことが明らかになった。これの何がまずいか。新しい辺を空っぽの2番目のキューに追加すると、そのキューから選ばれた辺が次に繋ぐ辺の一方として必ず使われるんだけど、これがコスト最小であるとは保証されない。1番目のキューから取り出されたものを2番目のキューに入れるからコスト最小が保証される。一方でこれが WA の原因にならない理由もわかってきた。どれか1つ任意の連結成分を選んで、これに属する辺を2番目のキューに入れると決めることでも N-1 回の操作で木を構成することができる。自分は UnionFind を使っていながら、ミスによって UnionFind による連結成分の管理を必要としない木の構成法を実行していた。なんだかなあ。考えが2つ足りず(次数への着目、インクリメンタルな木の構成法)、実装ミスをし(追加するキューの選定)、ミスしたと勘違いをしていた(IMKK)。素直に F 問題の AC を喜ばせてはくれないのか。


2024年06月21日 (金) X で見かけた。「階段を1段もしくは2段ずつ上るときに、ある段に至る上り方はフィボナッチ数になる」らしい。えっそうなんだって新鮮な驚きだったよね。DP の遷移から漸化式を見ようとしたことがなかった。2次元以上になるとまた何も見えなくなるんだけども。


2024年06月15日 (土) [AtCoder] 今日は CodeQUEEN 2024 予選 (AtCoder Beginner Contest 358) があった。良くないできではあるがしかたがないというあきらめもある。F 問題はどうにもならない。ではふりかえりを。■A 問題「Welcome to AtCoder Land」。比較します。「空白」を 0x20、改行を 0x0A だと決め打ってもいいよね。しなくていい split はしない。■B 問題「Ticket Counter」。前の人から順番にシミュレートすれば答えになる。■C 問題「Popcorn」。制約が小さいので組み合わせる数を決め打ってから全組み合わせを試した。考えなくていいので隙あらば全探索。またの名を総当たり、ブルートフォース。■D 問題「Souvenirs」。スーヴェニール? ou をウと読ませるのはおフランスっぽい。ジュルナリステン、カムフラージュ、ウイノン…………以上! つい最近までこれがお土産だとは知らなかったので、なのにお土産っぽい雰囲気がしたので、こういう名前の問題があったのだとわかる。思わず問題名に2って付いてないか確かめた。日記を書いている今調べたら、該当する過去問は ABC286-E Souvenir であり、あちらは単数形、こちらは複数形という違いがあったのだった。まあいいや。値段と個数を1つのパラメータで決めてしまうってなかなかおかしいね。二度見した。要求を下回らない限り最も安いものを選べばよい。B の昇順に処理を進めるなら A も昇順にスキャンするだけで済む。B の降順だとそうはいかないし、正当性にも不安が生じる(実際には問題ない気がする)。■E 問題「Alphabet Tiles」。問題を勘違いしていて解法が間違った枝に入り込んでしまって余計な時間を使った。どこに分岐路があったか。作る文字列は長さ K に限らず、1以上 K 以下であれば良いのだった。最初の間違った解法は、K 文字のうち k 文字が確定している場合の数を文字種ごとに数え上げていく DP だった。長さ K の答えは合っていたけどその他の長さは合わない。長さ K の答えは合っていたので、同じ方法を K 回繰り返すだけで答えが出るのはわかる。しかしこれはまともな時間で完了しなかった。1から K までの一連のプロセスを通じてそれぞれの答えを見つけなければいけない。インクリメンタルに答えを出していかなければ間に合わない。そうすると必然的に DP で持つべき状態が決まる。あとは状態と状態をつなぐ遷移がどうなるか。しばらく悩んで見つけた次の解法は、k 文字が確定しているときにある文字を1から c 文字まで挿入して k+1 から k+c の文字列を作る DP。答えは合ったけどローカルで 30 秒以上かかる。自分の PC で 16 秒だったら2秒制限でも AC になるのは ARC179-B の精進でわかっている(20240602)。システムを HDD から SSD に移し替えたときにジャッジサーバーとの速度差が2倍程度に追いついたのだけど、最後の言語アップデート以来、えっと、8倍に広がったの? 30 分以上高速化に費やして結局 AC までに1時間ちかくかけた。厳しい。何をやったか。コンビネーションの前計算をして、それだけでなく共通項をループの外に括りだして掛け算の回数を減らしたり、DP 配列の複製を抑制して in-place で値の更新ができるように処理順を入れ替えたりした。それでもコードテストで 3.5 秒だったり 2.5 秒はかかる。AC 提出は #54600449 なのだけど、最後の決め手は 20 行目の %P の位置だった。3項の掛け算のあとに %P をくっつけていたのを前にずらして2項の掛け算にするだけで2秒オーバーが1秒未満になった。Ruby にはオーバーフローがないからついテキトーに %P を付けたり省いたりしてしまうんだけど(%P が多すぎても少なすぎても TLE の原因になるのだ)、同じ1回の %P でも位置によって効果がだいぶ違うんだなあ。■F 問題「Easiest Maze」。出力形式難しすぎ。そして問題文が間違っていた。「壁があるときに辺で結ばれていて、長さ K の唯一のパスを構築する(大きさ K の連結成分では十分ではなく、塊やループや3つ以上の端点があってはいけない)」というのでは壁の既成概念が崩壊する。質問と訂正があるのを待つことにして A 問題からの実装を始めることにした。終了の 25 分前に戻ってきたら問題文の修正は終わっていたが、差分を調べるのが面倒で読まなかった。すること自体は難しくなさそうに見えてやりきるのはすごく難しいかも。自分の頭で考えて答えを構築するなら、縦と横の偶奇の扱いがやっかい。だったら機械に長さ K のパスを探索させれば何も考えなくていい。100×100 というのが素朴な DFS にとってどれだけの規模感なのかよくわからない。やばいとは思う。マスの埋まり具合と現在位置でメモ化してもどうか。2、3日前に読んでいた『競技プログラマー ハンドブック』(PDF) で紹介されていた枝刈り手法が使えそう。壁にぶつかって左右に進路が分かれるとき、ゴールがない方の進路に答えはない。なんにせよ時間がなかった。■精進。G 問題「AtCoder Tour」。F より低い青 diff なのを見たので問題を読んだ。現在のマスが終着点だと仮定したときの想定楽しさを元にした BFS でできそう。提出 #54618901 (AC / 413 Byte / 480 ms)。できた。こんな腑抜けた G 問題を置かれると、目の前の問題を解く以外のことを考えさせられてめんどうくさいんだよなあ。■■■精進。F 問題。やっぱりね、「すること自体は難しくなさそうに見えてやりきるのはすごく難しい」。何をするかって、ぐねぐね左右に線を引いて、ラスト2行の帰り道では縦にぐねぐね線を引いて、それをフォーマットして出力する。がんばってやる! 提出 #54644107 (AC / 1676 Byte / 51 ms)。右へ線を引く、下に移動する、左へ線を引く、ぐねぐねしながら左へ線を引く、と関数を分けることで、同時に1つのことだけを考えればいいようにした。文字列化するのも、行を出力する関数と行間を出力する関数に分けて、同時に1つのことだけを考えるようにした。もーたいへん。縦横の偶奇と K の偶奇は調べなかった。予め達成可否を判定することはせずに(できませんから!)、やるべきことをやった結果ゴールにたどり着いているかどうかで判断をした。これは ABC347-D の反省から(「RE/WA を出した方では、不可能なケースを最初に判定するようにしていた。一方の AC 提出の方では、操作をしたあとで、結果が満足しているかどうかで出力を切り替えた。要するに、不可能なケースの判定に漏れがあったのだ」)。■■■F 問題のジャッジがザルだったらしい。まあ、あの大作の出力形式を整えて提出しただけで評価されてもいいよ。■G 問題。「現在のマスが終着点だと仮定したときの想定楽しさを元にした BFS でできそう」について書く。まず、待機するマスは必ずパスの末尾になる。最適を目指すとそうなる。パスがあって長さが決まっていて、残りのターンを費やすのは報酬が最大のマスに決まっている。それがパスの途中にあるとするなら、そのマス以降のパスが蛇足だったということになる。移動を省いてそのターンも待機に費やした方が良い。だから待機マスは必ずパスの末尾になる。▐あるマスに到着したときのことを考える。関心事はそのマスを最終地点とした場合のスコアだから、到着時点での残りターンを報酬に変換してスコアを比較する。スコアがすでに記録されているスコアと同じか下回っている場合は探索を打ち切って良い。なぜか。パスを伸ばす目的というのはより良い待機報酬を求めることにあるのだから、あとから訪れていて残りターン数が少ない探索の枝はそれだけでビハインドを背負っている。それなのにパスに依存したスコアが余分に費やしたターン数に見劣りしているというのだから、これ以上の探索で先達より良いスコアを得る可能性はない。▐もう全部書いた? 提出したあとになって問題があると不安になって、でも実は問題がなかったことがある。BFS で t ターン目の処理をしているときに、隣接マスを t+1 ターン目のキューに入れると同時に t+1 ターン目の状態をその隣接マスに書き込んでいる。このあとさらに t ターン目の処理を進めているときに、さっき t+1 ターン目のキューに追加したマスが出てきてしまったら、t+1 ターン目の状態に基づいて t ターン目の処理をすることになるのではないかと考えた。でもチェス盤を考えるとわかるんだけど、t ターン目のキューと t+1 ターン目のキューに同時に入るマスは存在しないのだった。うまくできてるんだなあ。今までそれを知らずにグリッド BFS を書いてたんだぜ。■■■D 問題。B の降順だと A をスキャンするだけでは済まないみたいなことを書いたけど、具体的には二分探索と中間位置からの効率的な削除が必要だと、平衡二分探索木みたいなものが必要だと思っていたけど、そんなことはなかった。昇順でも降順でも線形時間でソート部分の O(NlogN) で解ける。B の降順に処理を進める場合にはスタックを新しく1本用意する必要があるが、それだけでよかった。提出 #54693460 (AC / 降順)。だけどあえて降順ソートをするより素直に昇順に処理をする方が簡単。提出 #54576260 (AC / 昇順)。■G 問題。ダイクストラ法とのツイート(?)を読んだのでプライオリティキューを使う方法を考えた。グリッドが小さいから BFS でも大丈夫だと思ったし実際大丈夫だったけど、BFS だと後追い更新が続くとターンが進んでもキューが短くならないんだよね。提出 #54698671 (AC / 1132 Byte / 124 ms / 58 ms)。プライオリティキュー由来の log が付いても 480 ms より速くなった。ちょっと意外。しかも 124 ms というのはテストケース1つ目に特異的なタイムに見えるので、実質的には2番目に遅い 58 ms が最悪タイムだと見ていいと思う。爆速。キューにはグリッドのマスと同じ数だけ K ターン待機した場合のスコアを入れた。ゴールからスタート地点を目指す。移動するごとに決まったスコア(ゴール地点の待機報酬)を引き現在位置の報酬を足していく。最初にスタート地点に到達したときのスコアが答え。負の辺とかは知らない。ダイクストラ法を1回だけやっている。