感觉离线求LCA就只是用了一个并查集而已,不知道为什么叫Tarjan了(明明Tarjan是用来求有向图Strong Connectivity的说)
来说说怎么用离线算法Tarjan来求LCA啦~
首先我们将读入的查询和查询的序号存起来,因为是离线算法的嘛~而且要存双向的!(为什么看了下边就懂了的)
我们从根开始dfs遍历整棵树,如果搜到叶子结点u了,那么就看下u是不是需要被查询的结点,如果是就看和u有关系的v结点是不是已经被遍历到了,如果是那么LCA(u, v)就是root[ v ]。root[v]是所有u和v所在子树的根结点,因为v已经合并到了目前深度最浅的子树的根结点上了!
所以我们需要有的操作就是在回溯的时候,将儿子结点合并到它的父亲结点上去。
真的模拟一边就懂了。(其实是我解释不太好啦,有种只可意会不可言传的feel...)这个图画的很好,模拟一遍就会懂的。
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
inline int read()
{
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -f; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
const int maxN = 500005;
int n, m, s;
vector<pair<int, int> >q[maxN];
bool vis[maxN];
int lca[maxN];
struct EDGE{
int adj, to;
EDGE(int a = -1, int b = 0): adj(a), to(b){}
}edge[maxN << 1];
int head[maxN], cnt;
void add_edge(int u, int v)
{
edge[cnt] = EDGE(head[u], v);
head[u] = cnt ++ ;
}
int root[maxN];
void init()
{
for(int i = 0; i <= n; ++ i )
{
head[i] = -1;
root[i] = i;
q[i].clear();
vis[i] = false;
}
cnt = 0;
}
int Find(int x) {return root[x] == x ? x : root[x] = Find(root[x]); }
bool Same(int x, int y) { return Find(x) == Find(y); }
void Merge(int x, int y) //将y合并到x上
{
x = Find(x); y = Find(y);
root[y] = x;
}
void Tarjan(int u, int fa)
{
vis[u] = true;
for(int i = head[u]; ~i; i = edge[i].adj)
{
int v = edge[i].to;
if(v != fa)
{
Tarjan(v, u);
if(!Same(u, v)) Merge(u, v);
}
}
if(!q[u].empty())
{
int siz = q[u].size();
for(int i = 0; i < siz; ++ i )
{
if(vis[q[u][i].first])
lca[q[u][i].second] = Find(q[u][i].first);
}
}
}
int main()
{
n = read(); m = read(); s = read();
init();
for(int i = 0; i < n - 1; ++ i )
{
int u, v; u = read(); v = read();
add_edge(u, v);
add_edge(v, u);
}
for(int i = 0; i < m; ++ i )
{
int u, v; u = read(); v = read();
q[u].push_back(make_pair(v, i));
q[v].push_back(make_pair(u, i));
}
Tarjan(s, 0);
for(int i = 0; i < m; ++ i )
printf("%d\n", lca[i]);
return 0;
}