ひとりで勝手にはじめた蟻本読書会 グラフ理論 最短路問題 ワーシャルフロイド法 蟻本読書会 の続きです。

蟻本ではグラフに関する章はダイクストラ法、ベルマンフォード法、ワーシャルフロイド法で終わっているのですが、面白い課題をみつけたので掲載します。「木上での全頂点対最短経路問題」と「最小共通祖先」です。

根付き木の最小共通祖先

根付き木において、ある2頂点の共通祖先(2頂点から根までのパス両方の上にある頂点)のうち、いちばん根から遠いものを最小共通祖先(LCA:Lowest Common Ancestor)と呼びます。

木上の 2 頂点 u, v の距離は、(根から u までの距離)+(根から v までの距離)- 2 ×(根から u, v のLCAまでの距離)です。

根から各頂点までの距離は幅優先探索や深さ優先探索で O(V)(V は頂点数) で求めることができます。では最小共通祖先はどのようにすれば求めることができるでしょうか?

オイラーツアーを使った方法

木の根から DFS の探索で訪れた頂点を、その順に記録することをオイラーツアーといいます。オイラーツアーで生成された頂点配列の特徴として「任意の連続する部分列に含まれる頂点集合が部分木になる」があります。

u, v を含み、LCAの祖先を含まない部分木は、最初に u が登場する部分と、最初に v が登場する部分の間の列の集合で表現できます。この中で最も根からの距離が小さいものを求めれば、それが最小共通祖先となります。

木において、最小共通祖先を求めるには(1)根から各頂点までの距離を求める前処理と(2)オイラーツアーを生成する前処理が必要です。

実践問題

D – 閉路

n 個の頂点と n – 1 本の辺からなる連結な無向グラフが与えられます。それぞれの頂点には 1 から n までの番号が順番にふられています。

グラフ理論において、このような条件を満たすグラフは木と呼ばれ、閉路を含まないという性質があります。このグラフに対し、元のグラフに含まれない追加辺 (a, b) を 1 つ追加したグラフについて考えてみるとこのグラフはちょうど 1 つの閉路を含みます。

あなたの仕事はそのようなグラフについて閉路の長さ(閉路に含まれる辺の数)を出力することです。ただ追加辺の候補はいくつかあり Q 個与えられるので、それら全ての候補について答えを出力してください。

入力されるデータ
N
x_1 y_1
x_2 y_2

x_N-1 y_N-1
Q
a_1 b_1
a_2 b_2

a_Q b_Q

1 ≦ N, Q ≦ 100,000

各頂点までの距離とオイラーツアーで最小共通祖先を求める

入力されたデータから木の隣接リストを生成します。そのあとGetDistancesメソッドで根を頂点 0 としてここからの各頂点までの距離を求め、結果を配列で返しています。そのあと GetEulerTourメソッドでオイラーツアーで訪問する順に頂点番号のリストを取得しています。

オイラーツアーで頂点 v が最初に登場するのは何番目かをすぐに取得できるようにするために、頂点vはindexs[v] を見ればどこにあるかわかるようにしています。また distances2 にはオイラーツアーの index から対応する頂点の最短距離を取得できるようにしています。

そのあとクエリーが飛んできたらオイラーツアーのなかで最初に a が登場する部分と、最初に b が登場する部分の間を調べ、この中で最も根からの距離がもっとも小さいを探しています。これが最小共通祖先の根からの最短距離となります。あとは木上の 2 頂点 u, v の距離は、(根から u までの距離)+(根から v までの距離)- 2 ×(根から u, v のLCAまでの距離)なので、これに新しく追加した辺の長さである 1 を追加したものを返しています。

ただ、これだと制限時間以内に終わらないのではないかと思われます。1 ≦ N, Q ≦ 100,000 という制約があるため、最大で長さが 100,000 ある区間で最小値を探す処理を 100,000 回繰り返すことになります。たぶんダメだと思います。

distances2 の [start, end] の区間から最小値を探すという処理を短時間に何度も繰り返すことになるので、これに対応できる処理を書く必要があります。

平方分割で大量のクエリに対応できるようにする

どうするか? 平方分割はどうでしょうか?