关于Tarjan算法的讲解:https://www.cnblogs.com/JVxie/p/4854719.html
重点:
下面详细介绍一下Tarjan算法的基本思路:
1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.合并v到u上。
5.寻找与当前点u有询问关系的点v。
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
模板基于的题目:POJ 1330
#pragma GCC optimize(2)
#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
// #include <bits/stdc++.h>
#include <cstdio>
#include <vector>
using namespace std;
typedef pair<int, int> P;
const int N = 10010;
vector<int> G[N]; // 存树
vector<P> Q[N]; // 存询问,first为点,second为此询问的编号
int used[N]; // 保存每个节点是否访问过
int deg[N]; // 存每个点的入度
int ans[N]; // 存每次询问的结果
int pre[N]; // 并查集
void init()
{
for(int i=0; i<N; i++) // 初始化
{
used[i] = 0; // 所有点初始化为未访问过的
pre[i] = i; // 并查集初始化
ans[i] = 0; // 所有结果初始化为0,因为点是从1开始的
deg[i] = 0; // 所有点的入读都初始化为0
Q[i].clear(); // 清空询问
G[i].clear(); // 清空树
}
}
int find(int x) // 并查集,寻找掌门人
{
if(x == pre[x])
return x;
return pre[x] = find(pre[x]);
}
void join(int a, int b) // 并查集,合并两个点
{
int fa = find(a);
int fb = find(b);
if(fa != fb)
pre[fa] = fb;
}
void lca(int u) // 最近公共祖先,u为根节点
{
if(G[u].size() == 0) // 如果当前节点没有子节点,就标记此点一访问过
used[u] = 1;
for(int i=0; i < G[u].size(); i++) // 如果此点有子节点,先向下递归访问子节点
{
lca(G[u][i]);
join(G[u][i], u); // 将子节点并入到父节点中
used[u] = 1; // 子节点访问过后标记此点已访问过
}
for(int i=0; i < Q[u].size(); i++)
{
if(used[Q[u][i].first]) // 如果一对询问点中的另一个也访问过
{
ans[Q[u][i].second] = find(Q[u][i].first);
}
}
}
void solve(int n)
{
for(int i=1; i <= n; i++)
{
if(deg[i] == 0) // 根节点是入度为0的点
{
lca(i);
}
}
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
init();
int n, m;
int a, b;
scanf("%d", &n);
m = n -1;
for(int i=0; i<m; i++)
{
scanf("%d %d", &a, &b); // a到b有一条边
G[a].push_back(b);
deg[b] ++;
}
int q = 1; // 1组询问
for(int i=1; i <= q; i++)
{
scanf("%d %d", &a, &b);
Q[a].push_back(P(b, i));
Q[b].push_back(P(a, i));
}
solve(n);
for(int i=1; i <= q; i++)
{
if(ans[i] != 0)
printf("%d\n", ans[i]);
else
printf("Not Connect\n");
}
}
return 0;
}