$\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 もあってるでしょう...