$\newcommand{\O}{\mathrm{O}}$
木上の任意の $2$ 点間の LCA (lowest common ancestor) を効率的に求めることができるアルゴリズム. また同時に任意の $2$ 点間のキョリも効率的に求めることが可能になる.
実装は $2$ 種類 (euler tour + RMQ か ダブリング) あるが, 以下では euler tour 上での RMQ を用いて求める方法で実装している.
アルゴリズムについては蟻本かこの pdf の説明がわかりやすい.
(関数)
add_edge$(from, to)$ : $from, to$ の間に無向辺を追加する
build$()$ : 加えられた辺の情報を用いてデータ構造を構築する
lca$(u, v)$ : $u, v$ の LCA を求める
dist$(u, v)$ : $u, v$ 間のキョリを求める
時間計算量: 構築 $\O (n)$, 各クエリ $\O (\log n)$
template<typename T> class segtree { private: int n,sz; vector<pair<T, int> > node; public: void resize(const vector<T>& v){ sz = (int)v.size(); n = 1; while(n < sz){ n *= 2; } node.resize(2*n); for(int i = 0; i < sz; i++){ node[i+n] = make_pair(v[i], i); } for(int i=n-1; i>=1; i--){ node[i] = min(node[2*i], node[2*i+1]); } } pair<T, int> query(int a,int b) { pair<T, int> res1 = make_pair(numeric_limits<T>::max(), -1); pair<T, int> res2 = make_pair(numeric_limits<T>::max(), -1); a += n, b += n; while(a != b){ if(a % 2) res1 = min(res1, node[a++]); if(b % 2) res2 = min(res2, node[--b]); a >>= 1, b >>= 1; } return min(res1, res2); } }; class LCA{ private: int V; vector<vector<int> > G; vector<int> ord,depth,id; segtree<int> st; void dfs(int u,int p,int k){ id[u] = (int)ord.size(); ord.push_back(u); depth[u] = k; for(int v : G[u]){ if(v != p){ dfs(v,u,k+1); ord.push_back(u); } } } public: LCA(int node_size) : V(node_size), G(V), depth(V), id(V, -1){} void add_edge(int from,int to){ G[from].push_back(to),G[to].push_back(from); } void build(){ ord.reserve(2*V-2); for(int i = 0; i < V; i++){ if(id[i] < 0){ dfs(i,-1,0); } } vector<int> stvec(2*V-2); for(int i = 0; i < 2*V-2; i++){ stvec[i] = depth[ord[i]]; } st.resize(stvec); } int lca(int u,int v){ return ord[st.query(min(id[u],id[v]),max(id[u],id[v])+1).second]; } int dist(int u,int v){ int lca_ = lca(u,v); return depth[u] + depth[v] - 2*depth[lca_]; } };
lca の verify
AOJ : Lowest Common Ancestor
提出コード
dist は verify できていないが lca あってたら dist もあってるでしょう...