Processing math: 7%

AtCoder Regular Contest 141 E問題メモ

AtCoder Regular Contest 141

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

E - Sliding Edge on Torus

問題

  • N×N の正方形状に頂点が並んだグラフを考える
    • 平面座標のように (0,0)(N1,N1) で頂点番号を表す
  • グラフには、はじめ、辺はない
  • Q 個のクエリが順に与えられるので、以下のように辺を追加する
    • クエリは a,b,c,d で与えられる
    • k=0N1 について、((a+k) \mod N,(b+k) \mod N)((c+k) \mod N,(d+k) \mod N) 間に辺を追加する
  • 各クエリ後における、グラフ全体の連結成分数をそれぞれ答えよ
  • 2 \le N \le 2 \times 10^5
  • 1 \le Q \le 2 \times 10^5

解法

頂点とクエリの変形

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) となる。

  • a'=0
  • b'=(b-a)\mod{N}
  • c'=(c-a)\mod{N}
  • d'=(d-a-c')\mod{N}

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

列ごとの連結成分数

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

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

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

同列に対するクエリ

必ずしも列の全ての頂点が連結になるとは限らない。
たとえば N=6c'=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) の辺は、それぞれ

  • 【A】は(0,0)と(3,4)=2段下を結ぶ。GCD(6,2)=2
  • 【B】は(0,0)と(3,4)=1段下を結ぶ。GCD(6,1)=1

となり、結果の連結成分数と一致する。
要は、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_2g の倍数であり、変化しないので大丈夫。

統合済みの列同士の統合

さて、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 などと呼ばれるデータ構造が役に立つ。

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

  • 各頂点に、整数値 A_i を割り当てていく(この問題ではシフト値に相当)
  • いくつかの (x,y) について、「A_y-A_x=d である」という条件が決まっている
  • 条件を順に適用していき、任意のタイミングで、各 A_i を、その時点までの条件に矛盾しないような状態に保つことができる
    • たとえば、(w,z) を指定して、A_z-A_w が一意に決まるか判定し、決まるならその値を得ることができる

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

  • 未統合なら
    • b'd' の現在のシフト値 S_b,S_d を得る
    • d' の側の列グループ全体をさらに S_b+c'-S_d だけシフトさせつつ、2つの列グループを統合する
    • 両者の統合前の連結成分数のGCDが、統合後の列グループの連結成分数
  • 統合済みなら
    • b'd' の現在のシフト値 S_b,S_d を得る
    • c'-(S_d-S_b) が、シフトを考慮した上での「何段下と結ぶか」を示す値となる
    • 自身の列グループの連結成分数を、上の値とのGCDに更新する

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

Python3

programming_algorithm/contest_history/atcoder/2022/0529_arc141.txt · 最終更新: 2022/06/01 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0