目次

AtCoder Regular Contest 141 E問題メモ

AtCoder Regular Contest 141

最近のARC、青・黄くらいの難易度帯が少ない。

E - Sliding Edge on Torus

E - Sliding Edge on Torus

問題

解法

頂点とクエリの変形

1つのクエリで辺が追加される頂点のうち、 $((a+k) \mod N,(b+k) \mod N)$ の頂点をまとめて $(a,b)$ 側の頂点、もう一方を $(c,d)$ 側の頂点と呼ぶことにする。

追加する辺が斜めにずれていくのがややこしいので、頂点の並べ方を、↘が↓となるよう以下のように変形する。

(0,0)  (0,1)  (0,2)  (0,3)
(1,1)  (1,2)  (1,3)  (1,0)
(2,2)  (2,3)  (2,0)  (2,1)
(3,3)  (3,0)  (3,1)  (3,2)

こうすると、$(a,b)$ 側、$(c,d)$ 側がそれぞれ同じ列になるので、ちょっと考えやすくなる。

また、クエリ $(a,b),(c,d)$ を上端にシフトして、$a=0$ にして考える。こうしてもクエリに対する操作に変化はない。

クエリを $a=0$ にシフトした上で、改めて $b,d$ を 「変形後のグラフ上で左から何列目か」という情報に置き換えた値を $(a',b'),(c',d')$ とする。

たとえば、$(a,b),(c,d) = (1,3),(2,3)$ の場合、シフトして $(0,2),(1,2)$ となり、 さらに列を置き換えて $(a',b'),(c',d')=(0,2),(1,1)$ となる。

以降、「列」などの表現は、変形した後の並びにおけるものを表すとする。

列ごとの連結成分数

全ての頂点を表現することはメモリ的にできないので、列ごとの連結成分数を記録しておく。

はじめは、全列 $N$ であり、連結成分数は $N^2$ である。

初期状態に「同列に対するクエリ」「異なる列に対するクエリ」が来たときにどうなるか考える。

同列に対するクエリ

必ずしも列の全ての頂点が連結になるとは限らない。
たとえば $N=6$ で $c'=3$ だった場合、3個おきにグループができる。(連結成分は3個となる)

(0,0)┐      連結成分数 6→3
(1,1)┼┐
(2,2)┼┼┐
(3,3)┘││
(4,4)─┘│
(5,5)──┘

一般に、その列の現在の連結成分数が $p$ だった場合、新たな連結成分数は $GCD(p,c')$ となる。

全体の連結成分数 $ans$ は、$ans-p+GCD(p,c')$ に更新される。

異なる列に対するクエリ

列は統合される。
いま、考察の対象とするのは、あくまで双方の列が、まだ他の列と統合されたことがない場合に限るとする。

一般に、統合される前の列の連結成分数が $p,q$ だった場合、新たな連結成分数は $GCD(p,q)$ となる。
全体の連結成分数 $ans$ は、$ans-p-q+GCD(p,q)$ に更新される。

      2      3        ←クエリ前の連結成分数
  ┌(0,0)─(0,1)┐      
┌┼(1,1)─(1,2)┼┐    クエリ後はGCD(2,3)=1 のため、
│├(2,2)─(2,3)┼┼┐  連結成分数は1(全体が連結)になる
├┼(3,3)─(3,4)┘││
│└(4,4)─(4,5)─┘│
└─(5,5)─(5,0)──┘

Union-Findなどで統合列を管理するとよさそう。

クエリにより統合された複数の列を、「列グループ」と呼ぶこととする。

異なる列に対する複数のクエリ

統合されたことのない列同士なら上記のような感じで良いのだが、実際は、統合済みの列がさらに統合されたり、同じ2列の異なる頂点にクエリが来たりする。
この処理をどうするかが難しい。

Union-Findで $b',d'$ が同じ列グループだったとする。

その時、列グループの現在の連結成分数が $p$ なら、クエリ後は $GCD(p,c')$ になる?かというと、そうでもない。

【A】           【B】
                    \ \
(0,0) `(0,1)    (0,0)\ (0,1)
     \             \ \
(1,1)  (1,2)    (1,1)\ (1,2)
     \             \ \
(2,2)  (2,3)    (2,2)\ (2,3)
     \             \ \
(3,3)  (3,4)    (3,3)\ (3,4)
     \             \ \
(4,4)  (4,5)    (4,4)\ (4,5)
     \             \ \
(5,5)  (5,0)    (5,5)\ (5,0)
     `              `

この両者、いずれも連結成分数は6だが、ここに (0,0)→(3,4) の辺を新たに張ると、$GCD(6,3)=3$ にはならず、 連結成分数は【A】は2、【B】は1になる。

この両者を区別するような、データの持たせ方をしないといけない。

ここで、「異なる列グループの間に最初に張る辺は、必ず平行な辺とするよう、一方をシフトさせる」ことを考える。

すると上記はそれぞれ以下のようになり、

【A】           【B】
(0,0)─(1,2)    (0,0)─(2,3)
(1,1)─(2,3)    (1,1)─(3,4)
(2,2)─(3,4)    (2,2)─(4,5)
(3,3)─(4,5)    (3,3)─(5,0)
(4,4)─(5,0)    (4,4)─(0,1)
(5,5)─(0,1)    (5,5)─(1,2)

(0,0)→(3,4) の辺は、それぞれ

となり、結果の連結成分数と一致する。
要は、$c'$ そのものではなく、「$c'$ と、過去に張られた辺との差分」とのGCDが必要で、 シフトすることでそれを図的にイメージしやすくなる。

あれ、GCDをとるのは最初に張られた辺との差分だけでいいの? 3本目が追加されたとき、2番目と3番目の差分は考慮できてなくない? と思うが、 $g=GCD(t_2-t_1,t_3-t_1)$ とすると、$t_1=a_1g+k,t_2=a_2g+k,t_3=a_3g+k$ のように表せるので、結局、$t_3-t_2$ も $g$ の倍数であり、変化しないので大丈夫。

統合済みの列同士の統合

さて、$N=6$ とし、「列1を基準にすると、列2は5のシフト」「列3を基準にすると、列4は3のシフト」という統合済みの列グループが2つあったとする。

列2・4の間に、「列2を基準にすると、列4は2のシフト」が必要な新たなクエリが飛んできたとする。

この場合、全体を統合した結果は「列1を基準にすると、列2は5、列3は4、列4は1のシフト」となる。($N$ でループする点に注意)

統合済みの1つの列グループに対し、一律にシフト値を増減させても関係性は変わらないので、 新たなクエリに矛盾しないように調整する必要がある。

しかし、列グループのサイズは $O(N)$ なので、毎回、一方の列グループの全ての値を書き換えていたら、$O(NQ)$ かかる。

ここで、重み付きUnion-Find、または、ポテンシャル付きUnion-Find などと呼ばれるデータ構造が役に立つ。

要は、以下のような特徴を持ったデータ構造である。

これを使って、クエリで指定された列 $b',d'$ が、、、

としていくとできる。
もちろん、ポテンシャルUnion-Findを使わなくても、クエリを2回走査して、1回目で全体に矛盾しないシフト値を求め、2回目でそれを使って実際に答えを求める、などでもよい。

Python3