/ 最近 .rdf 追記 設定 本棚

脳log[AtCoder: 2021-10-23~]



2021年10月23日 (土)

最終更新: 2021-10-26T00:58+0900

[AtCoder] AtCoder Beginner Contest 224/D,E

D 問題で TLE に苦しんだ。E 問題も TLE に苦しんだ。そのまま E 問題が解けなかったので F 問題は読めなかった。

 D 問題 8 Puzzle on Graph

全探索が許されそうな制約。重複チェックのためのハッシュ表のキーを文字列にするか配列にするかで悩んだ。結果的に文字列ベースの探索で AC になった。

 提出 #26768646 (TLE×1 / over 4 秒)
 提出 #26770107 (AC / 2275 ms)

欠けてる数字を9番目の要素にするのがちょっとした Tips. TLE 解消の決め手にはならなかったんだけど。

現在の状態からありうる次の状態(のうち初出のもの)をすべて候補にするという意味で感覚的に全探索と書いたけど、そういうのは幅優先探索と呼ぶらしかった。

 E 問題 Integers on Grid

時間は1時間ほど残っていたのに、結果的に1時間と 10 分が AC までに必要だった。

 提出 #26776942 (TLE×18)

D 問題の TLE×1 と違って全然惜しくない。どこの処理量が膨れ上がるかとソースを眺めてみると、遷移のための辺を逐一処理しているところがダメ。

遷移の方向は A の小さい方から大きい方に決まっているので、A の降順に遷移可能回数を数え、行と列にその回数をメモしておけばいい。今後この行(この列)から遷移先を探すものがあるならメモした回数だけ遷移できますよ、というメモ。A の降順に処理しているから参照したメモはいつでも有効。

ああいや、A の値が等しい別の点が書き込んだメモを参照しないようにだけ注意が必要。この辺の呼吸は珍しくないので心得たものである。

 提出 #26781610 (AC / 1230 ms)

あと 10 分あればなあ。

 提出 #26786787 (AC / 708 ms)

不要になった処理を消し忘れてた。

レートはちょっとだけ上がってる。しかしもっと上がりたかった。


2021年10月22日 (金)

最終更新: 2021-11-23T22:07+0900

[AtCoder] AtCoder Regular Contest 120C 問題 Swaps 2

精進ですよ。水 diff だけど難しい(まるで水 diff が簡単かのような……)。考えがまとまるまで1日かかった。

 問題の操作

隣接2要素をスワップして +1/-1 するというけど、考えやすいように複数の操作をまとめるとこう。

  1. ある要素 Ai を右に(左に)いくつか移動する。
  2. たとえば右に5移動するなら、移動先の値は Ai-5 になる。
  3. たとえば右に5移動するなら、飛び越された5つの要素が、移動先に空きを作るためと移動元の空きを埋めるために、1つずつ左に移動している。
  4. 左に1移動した5つの要素は値を +1 する。

 ポテンシャル

Ai の値は移動に伴って変化しているように見えるけど、実のところ位置に応じて決まった値をとっているに過ぎない。どういう値をとるかは、最初に位置 i で値 Ai をとっていた、ということで決まる。

A 数列の各要素が位置1でとる値をその要素のポテンシャルと呼ぶことにする。ポテンシャルから要素の位置を逆引きできるようにインデックスを作成しておく。

 B 数列

B 数列をスキャンしながら、要求するポテンシャルを計算し、該当する要素を A 数列の先頭に近いところから貪欲に引っぱってくる。

引っぱってくるに際していくつの要素と位置を交換することになるかは BIT で転倒数を管理することで数える。というか、知る必要があって管理している数字が転倒数と呼ばれている、が順序として正しい。

 提出 #26732769 (AC / 561 Byte / 491 ms)

難しい。これが水 diff ってどうかしてる。ちなみに Swaps の1はこれ>「Swaps」。黄 diff です。解けるまで9か月寝かせました(20191111p0120200826p01)。2は1日寝かせただけで解けたんだから、妥当なのか?


2021年10月10日 (日)

最終更新: 2021-11-15T21:43+0900

[AtCoder] エクサウィザーズプログラミングコンテスト2021(AtCoder Beginner Contest 222)

大反省回。未だに ABC で ABC の3完敗退を繰り返していることに驚きを隠せない。今回がそうだしついこの前の ABC219 もそうだった>20210918。ついでに言うと3年以上前の第2回参加回もそうだった。まるで成長していない……。戒めとして普段はとばす A 問題から振り返る。

 A - Four Digits

20 年前の自分だったら N の常用対数から N の桁数を求めてくっつける 0 の数を決定していた。

だけどとある WSH 関連の掲示板で、十分な数の 0 をくっつけてから必要な文字数だけ切り出す方法を知った。

 提出 #26434564 (AC / 25 Byte)

他の人の提出を見ると printf かそれに類するメソッドを使うものが多かった。Ruby で最初に提出した人は rjust を使っていた。目的にぴったりのメソッド(rjust)がある以上、それを使うのが最善だった。

 常用対数から桁数

こういうこと。

L = lambda{|n| Math.log10 n } # 正整数 n の常用対数
D = lambda{|n| L[n].floor+1 } # 正整数 n の桁数(10進表記)

p [D[99],L[99]]   #=> [2, 1.99563519459755]
p [D[100],L[100]] #=> [3, 2.0]
p [D[101],L[101]] #=> [3, 2.0043213737826426]

学校で対数を習っても理解が伴わないと計算問題はできてもこういう風に実用できなかったりする。Project Euler の 62 問目を二重ループで解いたりもする>20110308p01.02

実はまだよく分かっていないのは自然対数の底 e。なんだかこれを底にすることで累乗が掛け算になったり曲線が直線になったりして性質を変えずに扱いやすくなったりするらしいんだけど、そういうのは(文系学部に分類されるらしいことを少し前に知って驚愕した)経済学部の人に任せておきたい。

 B - Failing Grade

コメントのしようがない。スクリプトを読んで。

 提出 #26437560 (AC / 63 Byte)

 C - Swiss-System Tournament

制約が小さいので愚直にシミュレーションをすれば良い。

解答を作成するのに 20 分かけたのが良くなかった。出力フォーマットを勘違いしていて、求められているのが順位で並べた人番号だったのに、人番号順に順位を表示しようとして余計な手間をかけ、余計な手間を実装するのにもやけに手間取ってしまった。

 提出 #26449288 (AC)

 D - Between Two Arrays

解けなかった。シンプルな DP。直前の項がとった値ごとに場合の数が記録してあれば、現在の項において取り得る値と場合の数が数えられる。それがわかっていて解けなかった。

  1. 提出 #26458829 (WA×15)
  2. 提出 #26459583 (WA×15)
  3. 提出 #26461168 (WA×15)
  4. 提出 #26493159 (WA×15) 一夜明けて今日も WA

もうね、「なんでなん?」という感想しかない。これが違うなら正解が正解ではないと言い張ることしかできない。

 提出 #26493304 (AC) Range を等価な Range で置き換えました。

制約の下限が 0 なのは知っていた。知っていたしそのせいで Range の終端が -1 になることがあるのもわかっていたが、終端が始端よりも前にある「空の Range」で切り出した部分配列が、「空の配列」ではないことがあるだなんて想像だにしなかった。これは Ruby の罠である。こういうことだ。

range1 = 0..-1 # これで WA
range2 = 0...0 # これで AC
p range1.size #=> 0
p range2.size #=> 0

array = *0..9
p array[range1].size #=> 10
p array[range2].size #=> 0

配列の切り出しにおいて Range は単なる添字のペア [0,-1] として扱われている。Range としてのアイデンティティを奪われている。この仕様を知らなかったわけではない。ただ、認めがたい仕様なので自分では絶対に使わない仕様なのであり、意識の外だった。

 E - Red and Blue Tree

D 問題を諦めたのに E 問題もコンテスト中の提出は叶わなかった。

やることははっきりしている。できるかどうかは別としてやるだけの問題。

木において辺とは頂点集合を左右に分けるもの」だと前回の ABC に関連して書いたばかり。ある辺について注目したとき、A_i と A_{i+1} が異なる集合に属していれば A_i から A_{i+1} への移動に際してこの辺を通るのでカウント +1。

A_1 から A_2,..., A_m へと移動するとき辺ごとに何回通るかが数えられたら、今度は R−B=K を満たすような塗り方(辺の選び方)を数える。一度も通らない辺は青でも赤でもどっちでもいいので除外してあとで掛け合わせる。

 提出 #26470241 (WA×4 / TLE×3 / AC×37) 昨晩の提出

原因は想像だけど、頂点集合の管理に 1000 ビットのビットフラグ×1000 を使って TLE。後半で謎の DP をして WA。

 提出 #26494106 (AC / 1050 Byte / 183 ms)

頂点集合の管理に UnionFind を使うことを思い出した。後半の DP はシンプルになって、辺を赤く塗った場合と塗らなかった場合を一緒くたにハッシュ表に詰め込むだけ。

1050 Byte は書き過ぎかなと思ったけど、他の Ruby での提出も軒並み 1001 Byte, 2162 Byte, 1019 Byte, 1776 Byte だったから別に突出してはいなかった。

 F - Expensive Expense

前々回の ABC で見たのでこれを全方位木 DP と呼ぶことを知っている。そのときの経験でとりあえず根をとっかかりにすればいいことも知っている>20210928p01.01。根っこの特殊性は親がないことで、(子から親へ処理を積み上げているにも関わらず)親を参照しなければ求められない答えも、根に限れば求まる。そうすれば根を親に持つ子の答えも求まる。以下同様。

木 DP は処理の流れさえできあがれば、葉という一番単純で考えやすいところから差分で処理を積み上げていくと答えになるのでやりやすい。差分の部分の式が難しいことがあるし(20210909)、子のマージが難しいこともあるけど。

 提出 #26495540 (RE×5)

順調にジャッジが進んで行っていたのに最後の最後で次の一群のテストケースに阻まれた(06_n_eq_2_00.txt, 06_n_eq_2_01.txt, 06_n_eq_2_02.txt, 06_n_eq_2_03.txt, 06_n_eq_2_04.txt)。AC は近い。あそこが nil になりそうだなーと疑っている部分はある。

 提出 #26495783 (AC / 602 Byte / 1310 ms)

ABC が 12 時間のコンテストなら ABCDEF の6完も不可能ではない!(慰めはいらねーよ)


ゴルファー以外では Ruby で唯一の AC だった tinsep19 さんの提出 #26477920 を読んだ。

どうやら参照すべき日記を間違えていた。先月の 28 日(20210928p01.01)ではなくて、今月の 4 日(20211004p01)を参照すべきだった。全方位木 DP ではなく木の直径を解法とすべきだった。そっちの方が簡潔になるから。

関連問題である ARC022-C「ロミオとジュリエット」のスライドに2通りの解法が書いてあったらしいのをある参加者のブログで読んでいたが、わざわざややこしそうな全方位木 DP で木の直径を求める方法を知りたいとは思わなかったのだった。

知らずに実装していたし、知っていたのに(理解が浅くて)簡潔な手法を選べなかった。

振り返れば E,F がやるだけの問題だったこと、全方位木 DP、木の直径、どれも前回と前々回の ABC を彷彿させるものだった。どちらも復習はばっちりだった。その結果が3完とは泣かせる。


 提出 #26528881 (AC / 587 Byte / 1032 ms)

DFS 2回で直径を求めるバージョン。全方位木 DP バージョンと比べて、思ったより短くはならなかったけど速くはある。何より各ステップが単純でバグらせにくいと思う。

この単純さは、旅費が最大になる目的地の候補を予め2つに絞っているところから来ている。すなわち、直径(の1つ)の両端に位置する2つの街。

仮に木の中心がある辺にあるとしよう。すべての街が辺のあちら側かこちら側かに二分される。どの街をスタート地点に選んだ場合でも、中心を経由して、中心から最も遠く半径分だけ離れた街を目指すのが最も高くつく。中心を経由しないパスについては、中心に寄り道をするパスを想定して比較材料にすると、最も高くつくケースより安くなることがわかる。


2021年10月04日 (月)

最終更新: 2021-11-04T10:11+0900

[AtCoder] AtCoder Beginner Contest 221/F 問題 Diameter set

精進ですよ。おとといあった ABC の F 問題。

コンテスト時間中は木の中心についての理解がぼんやりで解答に至らなかった。そもそも木の中心などというものを考えたことがなく、でもサンプルの2のような円形の木で制限時間内に答えを数えきるためには、木の中心を中心とした組み合わせを考えるしかなかった。

木の直径については知っている。過去にある問題で満点解答のためのヒューリスティクスとして、深さを求める関数を2度呼び出して答えとしたことがあった。後にそれが運任せではなく確かな手段らしいことを知った。証明は知らない。

木において辺とは頂点集合を左右に分けるものだということを知っている。どの辺でもいいので1本選んで真ん中に横向きに置いて形を整えるとアレイ(亜鈴)型になるイメージ。コンテスト中には思い出せなかった。そのせいで問題の木を具体的にイメージする力が弱かった。このことは今朝のトイレで考えるでもなくふと思い浮かんだ。

たとえば直径が偶数の時、直径の中心には頂点が1つある。問題は直径を与える頂点ペアが複数あるときに中心が複数あるかどうか。中心が仮に2つあるなら、2つの中心の中点が本来の中心であるべきであり、直径だと思っていたもの、2つの中心だと思っていたものは直径でも中心でもなかったことになる。だから中心は1つ。

たとえば直径が奇数の時。直径の中心には1本の辺がある。この辺は中心に位置する唯一の辺だろうか。仮に直径の中心に位置する辺が2つあるなら、2つの辺の中点が本来の……(略)。だから中心は1つ。

色の塗り方だけど、中心から最も遠く半径と同じだけ離れている点の集合を、中心から直接出るどの頂点の先にあるかで分類する(中心が辺なら辺が結ぶ2つの頂点を考える)。同じ頂点の先にぶら下がっている2点を同時に塗ってしまうと、そのあいだの距離は必ず直径よりも短くなる。直径より長くなることはありえないし、仮に直径と等しくなることがあるなら、真の中心はどこだ?という話になる。そんなものはない。

ここまでを今朝のうちに納得してから実装したのに、直径が偶数のケースで中心の求め方を間違えたり(提出 #26352819)、線形時間の集計を繰り返して TLE になったり(提出 #26353052)、無駄に長さ N の配列確保を繰り返して TLE になったり(提出 #26353383)、いっぱい間違えた。

配列アクセスとハッシュ表アクセスだと配列の方が断然速いのだけど、初期化が1度で済まないなら、ハッシュ表の初期化コストの低さが効いてくるみたい。

 提出 #26353562 (AC / 750 Byte / 725 ms)

8問目の黄 diff AC。これより上は橙が1問だけだから、かなりのレア度なんだ。

ちなみに 水 diff だった E 問題 LEQ は、まだ TLE を回避する計算方法がわかっていない。

 木の直径

さっき書いた。

木の直径については知っている。(中略)。証明は知らない。

木の中心を念頭に置いて考えると直径を求めるアルゴリズムはこういうことだ。中心は、頂点の場合も辺の場合もあるけど、頂点集合を左右のどちらか(もしくは中心から直接出る辺のどれか)に振り分ける。任意の1点を始点に選んで最も遠い頂点を求める1回目の探索は、中心を挟んで異なる側にある、中心から最も遠い点を求めている。2回目の探索も同じ。同じ側に属する点が最遠点として選ばれることがないのは、中心に寄り道するパスを想定して比較材料にするとわかる。2回の探索で見つかったどちらの頂点も中心からは半径分だけ離れているから、そしてお互いに異なる側にあるから、合わせて直径になる。

 ある問題

さっき書いた。

木の直径については知っている。過去にある問題で満点解答のためのヒューリスティクスとして、深さを求める関数を2度呼び出して答えとしたことがあった。

エディタのログから「.max_by」を GREP したら該当するファイルが(たったの) 15 見つかったので、順番に調べてみた。「ある問題」とは ARC022-C「ロミオとジュリエット」だった。これが青 diff なんだからチョロい!(嘘です。調子乗りました)


2021年09月28日 (火)

最終更新: 2021-12-21T16:21+0900

[AtCoder] AtCoder Beginner Contest 220/F,E

昨日あった ABC。今回は全体に易化傾向で、D 問題がやるだけの茶 diff。F 問題でも水 diff。実は F 問題より E 問題の方が解かれていないけど、そちらも水 diff の範囲。ABC は5完6完を狙いたいところなんだけど、ABCD を 17 分4完(+うっかり余りをとり忘れて 1 WA)でレートは微減。あなたは残りの 83 分間何をしていたのですか?

 F 問題 Distance Sums 2

こちらが先に解けた。やり方はすぐにわかる。1か所だけ素直に求めて、あとは辺の左右にあるノード数の差を見ながら差分を更新する。すぐに実装できたかというとそうはいかない。どういう処理をどういう順番で並べると答えが次々生み出されてくるのか、とっかかりが掴めなかった。結局、根を1つ決めると木に深さという概念が生まれて、根の深さを0にしておくと全頂点の深さの和が根にとっての答えになった。これがとっかかりになって最後まで書けた。

 提出 #26188321 (AC / 572 Byte / 573 ms)

木の問題は深さと距離と子孫がそれぞれ Depth, Distance, Descendant なもんだから、いつも D が識別子として不足して困る。それに直径(Diameter)も追加で(ちなみにアクセントは i でも e でもなくて a らしいですよ)。

 E 問題 Distance on Large Perfect Binary Tree

83 分間合わないサンプル2を合わそうとしていました。明らかに同じような計算がノードごとに繰り返されるので、累積和の累積和を表引きして TLE を避けるのだと思っていた。ところが実際は一番内部の式が定数になったので(たとえば 2^a × 2^{N-a} が a の値によらないようなこと)、愚直解だと思っていたものがそのまま答えだった。

しかしその愚直解を合わせるのにも一生分の時間がかかるように思われた。死ぬ前に解けて良かった。3つくらい勘違いポイントがあった。組み合わせを半分(それとも2倍?)扱いしなければいけないのはどういうケースか(同じ深さの2頂点を組み合わせて距離 D を作る場合ではない)、それは頂点何個分か(N,D に足し引きして求まる数ではないし冪(数)でもない)。

 提出 #26199345 (AC / 203 Byte / 167 ms)

余りをとる数え上げ問題は正答までの距離が計れなくて途方に暮れがち。

愚直解っていうのは、深さごとに、頂点がいくつあるか、頂点1つが相対的深さ D の頂点をいくつ持っているか、相対的深さ 1 の頂点と D-1 の頂点をいくつ持っているか、相対的深さ 2 と D-2 ならいくつか……を数えて掛け合わせて足し合わせること。

E 問題も F 問題も青 diff でいいじゃないかというくらい苦労したけど、振り返ればどちらも考察は要求されていない(「すぐにわかる」「愚直解」)。ある意味やるだけの問題だった。やるだけ(できるとは言っていない)。


2021年09月25日 (土)

最終更新: 2021-09-30T13:08+0900

[AtCoder] AtCoder Regular Contest 127/A,B

 A 問題 Leading 1s

制約上の上限が 16 桁だっていうから、f の値の上限も 16。たとえば f(x) = 5 のとき、x は 11111 であるかプリフィックスが 111110 であるかのどちらか。f の値ごとに N 以下の範囲にそういう x が何個あるかを数える。桁数を固定すれば数えやすい。提出 #26096008

競プロ典型90問「025 - Digit Product Equation(★7)」の簡単バージョン。

 B 問題 Ternary Strings

頭の中がぐちゃぐちゃで解けなかった。終了直前の提出がこれ>提出 #26101512 (1 WA)。

とってもくやしいことに、1文字書き換えたら通りましたとさ>提出 #26105189 (AC)。2行目の N==1L==1 にしただけ。なんだよもー。もーもーもー。

一応考えたことを。最小化したい最大値の先頭の桁は2で決まっている。その後ろに0から N-1 までの N 個の数を3進数で表記したものをくっつける。これが2から始まる N 個の数。0から始まる N 個の数と1から始まる N 個の数は、桁ごとの制約を満たすようにテキトーに決める。これだけ。なんで書けへんの? 他の人の提出を見ると tr で 0,1,2 をローテーションするのが簡単そうだった。簡単にできることをすごく難しく考えていた。

驚いたことに、このふがいない有様でレートは上昇しているのである()。どれだけ低いレートに甘んじているかわかろうというもの。


2021年09月19日 (日)

最終更新: 2021-09-23T19:26+0900

[AtCoder] AtCoder Regular Contest 126

昨日の ABC に続いて今日は ARC があった。

昨日と同じく ABC の3完(+2WA)。24 時終了でもう 27 時になりそうだけどまだ結果が出ない……(水色に戻れたの?どうなの?)

 A 問題 Make 10

脳内で組み合わせを全探索して優先順位を付けて貪欲法で>提出 #25984785

 B 問題 Cross-free Matching

LIS が見えていなかったせいで配列に対して insert/delete_at を繰り返す効率の悪い実装になったけど、通るは通った>提出 #25988002 (911 ms)。

LIS ではないこの解答の形は「Shortest Path on a Line」と「蛍光灯」で書いていた>20200826p02

LIS にすると倍以上速くなってこう>提出 #26015260 (440 ms)。

 C 問題 Maximize GCD

最初にサンプルの3にだけ答えを返すように if で処理を分けた。そうすると残りのケースで K の高すぎる上限を気にかける必要がない。

次に GCD について。砂で隙間を埋めるように1ずつの操作ができるとなると、GCD が取り得る値について A 数列の素因数から推測できることは何もないような気がしてくる。じゃあ探索しますか?

  1. 考えてる途中でなぜか +1 の他に -1 の操作もできるつもりになっていて WA>提出 #25992664
  2. -1 の操作に対応した部分を削除して AC>提出 #25993948 (1248 ms)。
  3. C 問題も B 問題同様、制約と入力がガチガチに厳しくないのに助けられている。余分な log を削って 943 ms>提出 #26017330

どうもね、N 回のループの中身を対数時間の処理にするということが、N^2 のループに対する高速化だという意識が強すぎて、N 回のループで前処理をして N 回の本番ループを定数時間で済ませる方が速いという感覚が持てない。N が2×N になってもオーダーは変わらないけど、ループを2本に増やすことに無意識の抵抗があるらしい。

 D 問題 Pure Straight

残っていた 20 分で愚直解だけ>提出 #25995534

RE と TLE の背後に WA が隠れている可能性がないではないが、「Shift and Inversions」でやったようにうまい遷移と、それと省略を見つけて高速化すればいいのかなと。


2021年07月21日 (水)

最終更新: 2022-02-17T12:53+0900

[AtCoder] 第七回 アルゴリズム実技検定 過去問

第四回までの日記>20201111p01

課金ユーザーじゃなくてごめんね。@atgolfer1 に流れてきたツイートを見てはじめて問題が公開されていること、第七回が行われていたこと、終了していたことを知ったよ。

 A - チェックディジット

やるだけ。提出 #24382962

 B - 蒸気圧

小さい方を選ぶだけなんだけど、10 分以上切り捨てたり余りを取ったりして悩んでいたのは内緒。提出 #24383193

 C - 入力チェック

Ruby なので 10 進 100 桁はなんの障害でもなかった。提出 #24383281

 D - 書き換え

5つのパターンをじっと見れば最終結果に影響するような文字の取り合いがないことがわかる。提出 #24383341

 E - 青木君のいたずら

探索が許されていることと操作が1回に限られていること、それと遷移が足し算ではなく掛け算だという点が違うけど、問題の形としては ARC122 の C 問題 Calculator を思い出した>20210612p01.03。これが解ければ ARC122-C も解ける!

提出 #24383572

 F - ダブルブッキング

スケジューリングの問題だけど、求められているのが最適化ではなく判定だという点で、最も簡単な部類。提出 #24383711

 G - べき表現

20 分考えた。

どんな正の整数でも、テキトーに選んだ正の整数を基数としてその冪乗の和で表せるのは当然だけど(N 進数ってそういうもの)、使える数字が 0 と 1 と -1 に限定された 3 進数ではどのように事情が異なってくるか、という問題。

2×3^x = 1×3^{x+1} - 1×3^x

という感じで、2 を -1 に置き換えてから次の桁にツケをまわしていくのがいいでしょう。提出 #24384055

 H - 折れ線グラフ

解けてないよ。制約が小さいから探索していいのだろうけど、それでも 100 の 100 乗になるようなバカな探索は許されない。私はバカです。

 @2021-11-15 TLE→AC

上限の揃った制約が DP だと告げている。それも3乗の。まだ8問目だからって雑な探索で解けるとなめてかかっていたのではないか?

  1. 提出 #27281059 (TLE×4) 腰を据えてもこれである
  2. 提出 #27281255 (AC / 476 ms)

なだらかな折れ線グラフを書きたいのだから、遷移先の幅は自ずと限られる。(W = A.sum/[N-2,1].max+1 -2, +1 はテキトー)

 I - ほくろ

ABC-D 虐殺三銃士」のひとつ「Congruence Points」を思わせる問題だけど、両目の位置を手がかりに確定した情報が得られるところが優しい。

図形問題は行列や複素数に計算を丸投げしがちなので、これは自分で式を書いた。25 分かかった。提出 #24392172

 J - 終わりなき旅

強連結成分分解をしよう」がキーワードだった競プロ典型 90 問の 021 - Come Back in One Piece(★5)がすぐに思い浮かんだんだけど、深さ優先探索を2回するんだというアルゴリズム解説は何か所かで読んでたんだけど、これは 17 問ある★5問題のうちで解いてない3問の1つなのだった。

雰囲気は掴んでいたので雰囲気で書いた。提出 #24393748。細部が詰められてなくて無駄があるかも。競プロ典型 90 問のおかげでアルゴリズムの顔見せだけは済んでいたので、今度は実装するところまでこぎつけた。

 K - 急ぎ旅

理解が甘くて 1 TLE。実装ミス(< にすべきところを <= にしていた)で 1 WA。AC はこれ>提出 #24424698

最短経路問題ならダイクストラ法なんだけど、満足度と所要時間の2つを考えなければいけないのをどう考えるか。所要時間が最短であることが第一で、満足度の高さはその次に考慮すればいいだけなんだけど、だけど、「満足度について、同じ都市を 2 回以上経由してもその都市の景観は 1 回しかカウントしません」という文言がちょっとひっかかるよね。経路を記録して重複を排除しないといけないの?(それには無視できないコストが……)

もちろん負の移動時間はないから、同じ都市を2度訪れる経路が最短になることはない。だけど次の2つのようなパスで所要時間の合計が同じになる場合をどのように扱うか迷った。

都市 M に着いたときの満足度の総和は明らかに2、3、4を経由してきた経路の方が大きくなるんだけど、もう一方の経路は M のあとで7、8、9を経由することで取り返すことができるかもしれない。M に着いたときの満足度の低さを理由にして一方のパスを捨てていいのかどうか。

実は上の図には嘘があって、上の図のような状況の実際は下のようになっている。

これは1と M とのあいだの最短所要時間と、M と N とのあいだの最短所要時間がどちらも1つの値に決まることからわかる。最短経路であるどの2つのパスを取り上げても、分岐と合流を繰り返す第3のグラフのような関係になることが納得できたら、ある都市に最短で到着する経路のなかから最大の満足度を記録するのでいい。

 L - たくさんの最小値

セグメントツリーなんだけど、最小値を持っているインデックスをどうやって列挙するかという問題。

最小値の記録と添字の記録をセグメント木と配列で分担しようとして TLE を出したり(提出 #24408482)、セグメント木の実装ミスで大量の WA を出したりしつつ(提出 #24412768)、三度目の正直で AC>提出 #24413254

セグメント木を上から下に下るのって苦手。(根っこが上です。逆にすると頭が働かない)

 M - 分割

全部を分割した状態から始めて、損をしない範囲で統合していくのかなと思ったけど、並べ替えができなくて境界が入り乱れるのを、どう整理して扱えるのかわからない。

とある赤亀コーダーさんによると全体でこの M が一番難しかったらしい。

 N - モノクロデザイン

昨日これの簡単なバージョンを解いたよ>「B - すぬけ君の塗り絵 2 イージー」(提出 #24414821)。

二次元累積和かなと思うんだけど、ABC203-D Pond競プロ典型 90 問の 081 Friendly Group(★5)もまだ解いていない。二次元累積和って累積和の累積和とは違うんだなー、という感想を持ったのは覚えてるけど、よく思い出せない。

 O - コンピュータ

O 問題が解けたのは初めて。

入力を圧縮したり累積を記録したりして計算量を削減するのは、競プロ典型 90 問の 089 - Partitions and Inversions(★7)ぽいなーと思った。

以下は考えたこと。

  1. ある B より後ろにあって、前にある B と同じかそれより小さい B は無視できる。
  2. だから N 個の入力は、B の増加列といくつかの A を足してまとめたもののペアとして圧縮できる。
  3. 最終日までに得られる報酬の総額は決まっている。コンピュータに支払う金額をどれだけ減らせるかという問題。
  4. 十分な資金があるなら明らかに B の最大値と同じ値段のコンピュータ1つだけを買うのが最適。
  5. しかし資金が足りない場合は目の前の障害(B)を超えられるだけのコンピュータを買って、目先の報酬を得るようにしなければいけない。
  6. なんですかね、貧しさ故に最適な選択ができないって、現実ですか。保険とか追証とか宝くじとか。
  7. 複数の買い方。障害(B)が1、2、5、6、9と並んでいるときに、1を買って5を買って9を買うのがいいか、2を買って6を買って9を買うのがいいか。局所的な判断では決まらないので貪欲法は使えない。
  8. 迷うたびにキューを伸ばすと爆発するので DP っぽいなー。
  9. N^2 のループは許されていないので、すべての i についてそれより後ろにあるすべての要素を処理対象にすることはできない。
  10. i<j<k とする。コンピュータはできるだけ買わないのが正解だから、ある i と j がともに k にある障害を越えるというとき、j から k への遷移が i から k への遷移より得するということはない。
  11. i を通過する時点ですでに k を超えられるだけのコンピュータを買う資金があるのだから、到達地点が同じ k なら j で足踏みする価値がない。
  12. j で足踏みする価値は k より遠くへ(k を超えて一足飛びに)到達できる可能性にある。
  13. という感じであとはがんばってコーディングする。提出 #24429839
  14. .each_with_index.each とか謎なことしてる……。幸いにしてこれは無駄なだけで害はないけど、部分的な修正を繰り返すとこういう風に、(通して書き下したときにはありえない)謎なバグを仕込みがち。

2021年07月18日 (日)

最終更新: 2021-07-19T22:56+0900

[AtCoder]AtCoder Regular Contest 123C 問題 1, 2, 3 - Decomposition (+ B + A)

悔しいなあ。手も足も出ない方がまだあきらめがつく。

 提出 #24373137 (WA×20 / RE×12 / AC×2)

1時間かけてコンテスト終了直前に投げた。サンプルすら通っていないけど、5つあるケースの最後の1つの答えが違っているのに気がついていなかった。ダメダメである。

RuntimeError の原因は Array#compact が false を取り除いてくれていないのに気付かずそのまま min メソッドを呼んだこと。

Wrong Answer の原因は比較して小さい方を選ぶべきところで、勝手に優先順位を付けて先に値が得られた方を選んでいたこと。それともう1か所(b+(d-1)%10 の %10 が完全に余計)。

プログラムの型は Send More Money と同じ>20210412p01。桁を見て、キャリー(ボロー)を伝えて、最後まで矛盾なく桁が確定できるかどうか。それを k を増やして条件を緩和しながら最小値を探った。

答えの上限が5だと見抜いていたところは偉いと思うんだ。なぜ5かといえば、項数が1ならある桁に関して作れる数字が 1..3。2なら 2..6、3なら 3..9、4なら 4..12、5なら 5..15 ということで、最初に 10 種類の数字が作れるのが5項の和。0..4 の数字を作ろうとすると繰り上がりが不可避なのが制限だけど、それは隣の桁で吸収できる。6以上に項数を増やしてもできることは増えない。

 提出 #24375535 (AC)

ほとんど違いませんよ。でも A から F まで6問ある中の3問しか相手にしていないのに、時間内に完成させられないんだなあ。

実行時間から判断するにだいぶ無駄が多いみたいだけど、制約に苦しめられてたったひとつの解法、たったひとつの記述を探らないでいいってすばらしい。

 B 問題 Increasing Triples

ただの貪欲。提出まで 15 分>提出 #24363550

 A 問題 Arithmetic Sequence

AC まで 2 WA、33 分。どういうこと? 10 分時点で9割5分は完成していた>提出 #24355415。デバッグ出力を消し忘れてるのと、入力を勝手にソートしてるのがいけない。列(Sequence)は順序付き!(知らないわけではない。括弧と波括弧に使い分けがありそう? それは知らない)

AC>提出 #24361099。ほとんど違いませんよ(2回目)。


2021年07月17日 (土)

最終更新: 2022-01-25T19:28+0900

[AtCoder]AtCoder Beginner Contest 210E 問題 Ring MST (+D 問題)

A 問題から WA を出して、D 問題も 4 WA のち AC だったけど、D 問題が水 diff だったおかげで4完最遅に近かったはずでもそこそこのパフォーマンスがもらえた。棚ぼた。E 問題が解けなかった。

 提出 #24322478 (RE×11 / AC×19)

30 分考えて出したコンテスト中唯一の提出。ソースを見ればわかるけど、3分岐のうち2つしか埋まっていない未完成の状態。未実装部分に 1/0 (ZeroDivisionError) を埋めておくことでジャッジ結果を AC/WA の2択ではなく AC/WA/RE の3択にするテクニック。WA がなかったということで、中身のある2分岐にミスはなさそう。

 提出 #24337207 (AC / 273 ms)

複数の A 要素の関係について、中国剰余定理が関係してくる気がしてお手上げだと思っていたのだけど、GCD で良かったらしい。まだ理解していない。

 D 問題 National Railway

DP です。1行目から行ごとに考える。

  1. ある行について。処理済みの地点に建てた駅からこの行この列に至る最小のコストを記録することにする。
  2. その次の行にて。各列 Aij の費用を払って駅を建てたとする。前の行の j 列に記録されたコスト(の先にある地点)をもうひとつの駅として、建設費用を計算する。
  3. 建設費用の最小値が答え。

罠があるとすれば、同じ行から2つの駅を選ぶ場合を見落とさないこと。見落とさないこと。慌てて対応をミスって WA を重ねないこと。

 提出 #24314328 (AC / 559 ms)

m1 が同じ行内からもう1駅を選んだ場合の建設費用。m2 が前の行の同じ列から下に線路を引いてきたときの建設費用。

その他の処理は「この行この列に至る最小のコスト」を記録するためのもの。上の行から引っぱってきた線路を延長するのがいいか、Aij を払ってその場に駅を建てるのがいいか、同じ行の左右から線路を引いてくるのがいいか、最小費用を記録する。


ところでこの問題、「鉄道建設にかかる費用として考えられる最小値を出力してください。」で問題文が締められているのだけど、2、3回読み直したよね。え? どのような鉄道を敷けばいいのか示されてましたか。まさか「高橋君は国民の利便性を上げることよりも、鉄道建設にかかる費用を少なく抑えることを優先したいと考えています」が真実その通りで、1000000 マスのうち最低でたったの 2 マスをカバーする鉄道を敷設するだけでお茶を濁すつもりだなんて、一読しただけでは飲み込めなかった。高橋王国民甘すぎ。


2021年07月04日 (日) プレイ中。アルノサージュの世界はわかりやすい。巨乳は敵! 巨乳は敵! しかしイオンちゃんはそれなりに……。イオンちゃんは敵?(わかりやすくバカなのはお前だ)■アルノサージュは実質的にアルトネリコ4だと評するコメントがあった。アルトネリコの3は発売前から悪ノリが目に余って回避したが、1と2はプレイした。アルトネリコの1と2と4はおすすめできる。再発売される Legend of Mana の横に並べてもいい。特に1がおすすめです。あんなにプレイヤーをやきもきさせる心配なヒロインは初めてでした。■■■「「聖剣伝説 レジェンド オブ マナ」のHDリマスターを遊んだら軽く感情が暴走したので全力でお勧めする:今日書きたいことはこれくらい(1/2 ページ) - ねとらぼ」■■■「『聖剣伝説 レジェンド オブ マナ』HDリマスター版発売を機に、石井浩一氏&高井浩氏にインタビュー。オリジナル版開発秘話を訊く - ファミ通.com

最終更新: 2021-07-14T01:38+0900

[AtCoder] 競プロ典型 90 問/083 - Colorful Graph(★6)

たいへん厳しい。解説1解説2解説3解説4

解説を読めば半分全列挙と同じように、汎用的な手法であるが故に問題から解法を見出そうとしても出てこないタイプの問題と解法だったと言えるのではないか。

以下は解説を読む前の提出。

 提出 #23928493 (TLE×9 / 同じ内容) ※TLE×4 に改善できる

クエリを先読みして頂点ごとに関連するクエリ番号をためておき、メインループは辺について繰り返すことにした。メインループの中ですることは辺が結ぶ2頂点が持つクエリ番号列のマージ。色の伝播を担うのが辺だということと、現在の色を決めるのは直近のクエリだということに着目した解法。

解説1にも書かれているように、これは特定の頂点に辺とクエリが集中したときに処理時間をオーバーする。とはいえこの提出のマージ部分は不必要に時間をかけている。2つのクエリ番号列の長さの和に比例した時間ではなく、二分探索を繰り返してもう少し(<コレ)ましな時間にできる。その場合の TLE は(おそらく)4つ(random_challenge のうち2つと random_clique 2つ)>typical90_83_TLE4?.rb27

 提出 #23932276 (TLE×8 / 同じ内容)

この問題の悩みの種は、クエリに応じた色の変化を隣接ノードに「通知する」ことも、逆に隣接ノードに現在の色を「問い合わせる」ことも、制約から許されていないということ。

ここで、親にだけ通知してみるのはどうだろうと考えた。隣接ノードの数は N のオーダーになりかねないとしても、親であれば1つに決めていい。問題は親の決め方で、この問題のグラフは木ではない。

この提出ではノード番号が小さいものを親の側にあるとした。いきなり「親は1つ」ではなくなっているがしかたがない。だからこその TLE×8 なのだ。

 提出 #23933074 (TLE×5 / 同じ内容)

所与のノード番号を利用するアイディアはお手軽に過ぎたので、今度はちょっと手間をかけて深さ優先探索で親子関係を決め、閉路が見つかったときに限り余分な親を追加することにした。残る TLE は5つ。

テストケースをダウンロードしたら、TLE になっているのは random_clique と名付けられた全2ケースと random_kill と名付けられた全3ケースの合計5ケースだった。自分の解法に弱点があり、そこを見事に狙い撃ちされたといったところか。定数倍の改善では全然間に合わない。

 提出 #23996165 (TLE×2 / 同じ内容)

閉路が見つかったときにどちらをどちらの親にするかは好きに決めていい。親の数を比べて親の数が少ない方に他方を親として追加することにしたら、random_kill と名付けられた3ケースの TLE が解消した。

残るは random_clique が2ケース。random_kill が特定の1、2ノードに辺が集中していたのに対して、この2つのケースはまんべんなく多くの辺が伸びている。クエリに応答する負荷が全体的に底上げされていて逃げ場がない。

 提出 #24004617 (TLE×2 / 同じ内容)

クエリの先読みをしたらメインループの前準備で探索のためのスタックがいらなくなった。クエリに関与しない頂点は無視していいし、入力された辺も片方向だけ記憶しておくのでいい。どちらをどちらの親にするかを決める

# P[v].size は親の数。Qn[v] はクエリで参照される回数
if P[a].size*Qn[a] < P[b].size*Qn[b]

という判定はメインループの負荷をよく反映していて気が利いてると思う。すべてクエリ先読みの効果。でもダメ(TLE)です。さっきの TLE×2 からはローカルで 12 秒が 7 秒になったけど、ならジャッジサーバーでは良くて 5 秒だろう。制限は 3 秒。


2021年06月17日 (木)

最終更新: 2021-06-17T19:54+0900

[AtCoder]競プロ典型 90 問035 - Preserve Connectivity(★7)

解説はこちら>解説1解説2解説3

 LCA とダブリング

実装したことはないけどダブリング(要は繰り返し二乗法でしょ?)で効率的に親を辿る?」と書いたのがつい先月のことだけど、とうとうダブリングを実装する機会が訪れた。

この問題がどうして、選ばれた複数の頂点を決まった順番で環状に並べて隣接する頂点ペアの LCA が辺の本数になって割る2が答え、になるのかは解説3を読む。今は LCA にだけ注目する。

LCA の取り扱いは「巨大企業」や「筆塗り」で経験があるけど、線形より効率的な LCA の検索が案外面倒くさい。「最小共通祖先 [いかたこのたこつぼ]」にいくつか手法がリストアップされているけど、自分が知っている手段はセグメント木だけであり、セグメント木は書くのが面倒くさい。

そこでダブリングです。

 提出 #23482710 (TLE×26 / 810 Byte / 2207 ms / 42608 KB) ※まだコンテスト期間中であり見られないはず。

間違えた(TLE)。

A = [ps=P.dup]
(D.max-1).bit_length.times{ # 1世代上はすでに P としてあるので、1回分繰り返しが無駄かも。
	A << ps = N1.times.map{|a| ps[ps[a]] }
}

Ans = lambda{|a,dd|
	i = 0
	while 0<dd
		a = A[i][a] if 0<dd&1
		dd >>= 1
		i += 1
	end
	next a
}

A 配列に事前に親、親の親(2世代上)、4世代上、8世代上、とメモしておいて、Ans 関数(Ancestors の略ったら略)で「a の dd 世代上は?」という質問に対数時間で答えられるようにした。Ans 関数がこのようなインタフェースになっているのは、LCA を経由した2頂点間の辺の数を求めたい呼び出しもとで「同じ深さにある2頂点 a,b が初めて祖先を共有する深さは?」という問いを立てて、二分探索でその深さを確定させようとしたから。次のように。

Dst = lambda{|(a,b)|
	da,db = D[a],D[b]
	dc = (1..[da,db].min).bsearch{|dc|
		Ans[a,da-dc] != Ans[b,db-dc]
	}
	next dc ? da-dc+db-dc+2 : (da-db).abs
}

競プロにおいては対数は定数と言われるけれど、かなり大きな定数ではあり、必要がないところで log をくっつけると TLE に苦しめられたりする>「Pairs」。同じ日記に書いてあるけど、「射撃王」や「Handshake」のように余分な log があっても TLE にならないこともある。

 提出 #23482781 (AC / 873 Byte / 1082 ms / 42672 KB) ※まだコンテスト期間中であり見られないはず。

A 配列は正しくはこう使う。共有されている yuruhiya さんの解答 を見ても LCA#lca メソッドが同じ感じだったので、大嘘はついてないと思う。(Dst は Distance の略。D は Depth の略)

Dst = lambda{|(a,b)|
	da,db = D[a],D[b]
	a = Ans[a,da-db] if db<da
	b = Ans[b,db-da] if da<db
	A.reverse_each{|ans|
		a,b = ans[a],ans[b] if ans[a]!=ans[b]
	} if a!=b
	next a==b ? (da-db).abs : da+db-2*D[P[a]]
}

実は解説2にはこれを説明する具体的手順が書いてある(あとで詳しく読んだ)。だけどありとあらゆる落とし穴にはまりたがる自分にとって、「こうすればいいんですよ」とか「こうしてはいけませんよ」いうアドバイスは役に立たないんですな。「要は繰り返し二乗法でしょ」というだけの理解で実装してみて、「あれは良かったここはダメだった」と納得するまでは身にならない。仮にアドバイスのおかげで最初からうまくやれたとしても、それは今回だけのことであり、将来必ず自分の性向に導かれて穴にはまる。早いうちに地図を作っておくべきだ。そうすれば多少は、ね。


2021年06月16日 (水)

最終更新: 2021-06-21T21:06+0900

[AtCoder]競プロ典型 90 問068 - Paired Information(★5)

すごく難しいです。答えの出し方に確証が持てないまま、とりあえずグラフとして解答を書いた。クエリ0に応じて辺を追加する。クエリ1に応じてノードの値を確定しながら X から Y までグラフをたどる。しかし TLE。

 提出 #23506723 (TLE×10 / AC×3) ※まだコンテスト期間中でありアクセスできないのでこっち>提出 #23506723.rb27

次にクエリ0では Y=X+1 だという制約を思い出して、グラフを配列上に配置した。しかしこれでは多少効率が良くなれど TLE は解消しない。制約はノード数(N)、クエリ数(Q)ともに上限が 10 万であり、1つのクエリに答えるのに線形時間をかけることができない。

局所の変更を全体に(対数時間で)波及させるのに BIT がまず浮かぶ。何を記録しようか。階差の累積値かなと思った。つまり、連続する3要素 A,B,C があり、2度のクエリ0によって A+B=S, B+C=T であるとわかっているなら、C-A=T-S から1つおきの2要素の差が直接にわかる。これの累積を記録しておけば、クエリ1で X と Y が 2×k はなれていても直接計算できる。

 提出 #23510943 (AC / 669 Byte / 413 ms / 31888 KB) ※まだアクセスできないのでこっち>提出 #23510943.rb27

結局 BIT は使わなかった。クエリへの答え方は階差の累積という点で同じだけど、クエリの先読みをすれば事前に累積値が記録しておける。更新がないなら値の取得に対数時間がかかる BIT よりただの配列の方が良い。Ambiguous と答えるタイミングは Union-Find で管理した。

 《上級者向け(★7)》 X[i] + 1 = Y[i] の制約を外した場合(ワナにとても引っ掛かりやすいです)

階差を使う解法は完全に「Y=X+1 (T=0 の場合)」という制約に依存しているので、とっかかりを失って困ってしまった。グラフなら困らないが TLE。階差を封じられてどうすればよいか。

 A_1 と A_2、……、A_{N-1} と A_N の関係性がすべて分かっていた場合、A_1 の値を +1 すると、下図の通り +1 される要素と -1 される要素があります

解説が公開された。プラスかマイナスか、そんな単純な関係やったんか。それにしてもそこからさらに(厳しくない方の)制約をクリアさせられることがもうつらい。

Y=X+1 (T=0 のとき) という条件が外れた厳しい方の制約だけど、X から Y へ至る異なるパスがクエリ0によって与えられたときにどうすればいいかわからないでいる。定数時間で答えるためにどのような情報の持ち方をしておけばいいのか(グラフは TLE)。でもプラスかマイナスかという単純な関係なのならば、すべてのパスに関わるノードをまとめて詰め込んでおいて、つじつまが合うのだろうか。

連結なノード間であるノードを基準(たとえばゼロ)にして相対的な値を割り振っておく。ノード間の距離(の偶奇)も記録しておく。迂回路があっても分岐点・合流点のノードが持つ相対値はパスによらず共通だし、パスによって異なる非共通ノードも、ノード数の偶奇はたぶん同じになる。まだよくわからないのは、連結成分ごとに基準となるノードが必要だけど、連結成分同士が連結するたびに基準と相対値のふり直しをして許されるのかというところ。小さい方の連結成分を選ぶようにすればいいのだろうけど。

全部 Union-Find に乗せたいけどうまくいかない。Unite は当然として、Find で集合の代表を書き換えるタイミングでもごにょごにょすると、集合の合体があったとしても偶奇と相対値を適切に(新しい代表を基準にして)更新できると思うんだけど、符号で死ぬほどバグらせる。一見正しい答えに見えても、バグ×バグ=正常だったりする。

 https://github.com/monnu123/atcoder_files/blob/master/kyopro90-68.cpp (monnu さん / C++ / 166 ms)

自分がやりたいのはたぶんこういうの。やっぱりできるんだね。不可能をやろうとしてるのでないとわかったのは朗報。

ところで、Ruby のようなスクリプト言語と C++ の実行時間が接近しているときは、入出力がネックになって C++ の実行時間が遅くなっている印象がある。C ランタイムとの連携を切ったり、std::endl が引き起こす flush を "\n" で回避したり、いろいろ対処法がある模様。

 https://github.com/NachiaVivias/kyopro-tenkei-90-submit-nachia/blob/main/shared-solutions/068-Paired-Information/068-01.cpp (Nachia さん / C++ / 51 ms)

こっちの見た目すっきりな方はすっきりしすぎてて自分がやりたいことと同じなのかどうかもわからない。

ノード数が 2N あるなあ。前半 N 個と後半 N 個の位置づけとは。

偶数世界と奇数世界? これはノード番号 X が偶数か奇数かという話ではなく二部グラフの話なのであって、あるノード X を赤色で塗った場合と白色で塗った場合を同時並行で扱っているのではないか、という……あてずっぽうです。

 提出 #23560415 (AC / 778 Byte / 334 ms / 16544 KB) ※まだアクセスできないのでこっち>提出 #23560415.rb27

やった! うんざりするほどバグらせてついに完成した(UnionFind 全乗せ版)。頭が整理されたところでバグまみれを捨ててイチから書き直したのが良かったと思う(それからもバグらせたんだけど)。

	D[a] *= D[g]
	V[a] += D[a]*D[g]*V[g]

↑ここは無駄なことをしてる。修正版はこう↓

	V[a] += D[a]*V[g]
	D[a] *= D[g]

Union-Find でグループの代表とサイズを管理するのはこれまでのおなじみだけど、同時に代表からの相対値を記録するのは考えたこともやったこともなかった。「重み付き UnionFind」というものがあるらしいが、いったい何に使うものなのか。

ノード数が 2N のすっきり解法は遠くから拝むだけにしておきます。

 https://gist.github.com/masa-aa/4be96f053457dc60625a3552288fb1e4 (masa_aa さん / PyPy3 / 304 ms)

これも重み付き UnionFind。ノード番号の偶奇を利用すれば別途偶奇の管理をする必要がないらしい。クエリへの応答部分でだけ偶奇を気にすればいいというのがよくわからないが、たぶんこれを考えた先に Nachia さんのサイズ 2N のグラフがあると思う。

偶奇を一体でぐちゃぐちゃ扱う(自分)→偶奇に前提をもうけて整理されたグラフを作る(masa_aa さん)→偶奇と奇偶を並行させて偶奇の別を気にしない(Nachia さん)、という違いなのではないか、という印象を持っている。

あ find メソッドで再帰呼び出しをしていない。スタックオーバーフローのおそれがないのはいい。

これだけの学びがあるのは企画の趣旨に照らして(そうでなくても)、すばらしい良問だったのでは?