原篇:(ACM算法)tarjan算法求LCA - 知乎 (zhihu.com)
顾名思义,就是求两个节点最近的共同祖先,就好比下图,2和3的共同祖先为3,2和4的共同祖先为1。
关于LCA求解有3种算法。
1.标记回溯法(也称暴力枚举,从一个点开始向上标记他的父节点,直接标记到根节点为止,然后另另一个点也开始向上回溯,当这个点被标记过,则找到这两个点的共同祖先),当数据过大时,很明显这个算法会超时,所以本文不过多解释此方法。
2.ST倍增法(本文也不重点介绍次方法,读者可看其他博客阅读此方法)
3.targan算法(本文重点介绍)
tarjan算法就这几个步骤:
1.标记当前节点x已访问,但是还没有回溯
2.枚举所有没有访问的过的子节点y,继续遍历子节点
3.合并y到x上
4.访问所有与当前节点有询问并且回溯过的y x,y的lca就是find(y)
5.标记当前节点回溯
首先targan是基于并查集实现的,没学过的读者可点击此传送门:何为并查集?-CSDN博客
具体过程看以下代码:
#include<bits/stdc++.h>
#define int long long
#define endl "\n"
#define fi first
#define se second
#define PII pair<int,int>
using namespace std;
const int N=5e5+5;
int fa[N],vis[N];
vector<int> v[N];
vector<PII> query[N];
int ans[N];
int find(int x){//并查集模板
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void tarjan(int x){
vis[x]++;//第一次走过的路径标记为1
for(auto i :v[x]){
if(!vis[i]){//若子节点没走过 继续遍历子节点
tarjan(i);
fa[i]=x;//合并子节点到当前节点
}
}
for(auto t : query[x]){
int a=t.fi,b=t.se;
if(vis[a]==2&&!ans[b]) ans[b]=find(a);// 若已经回溯并且b下标为空
}
vis[x]++;//回溯的路径标记为2
}
void solve(){
int n,m,q;
cin >> n >> m >> q;
for(int i=1;i<n;++i){
int x,y;
cin >> x >> y;
v[x].push_back(y);//将x,y存入数组中
v[y].push_back(x);
}
for(int i=1;i<=n;++i) fa[i]=i;//预处理所有节点都是自己的父节点
for(int i=1;i<=m;++i){
int x,y;
cin >> x >> y;
if(x==y){//特判
ans[i]=x;
continue;
}
query[x].push_back({y,i});//存入查询的数字
query[y].push_back({x,i});
}
tarjan(q);
for(int i=1;i<=m;++i){
cout << ans[i] << endl;
}
return ;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
//cin >> t;
while(t--) solve();
return 0;
}
经典例题:P3884 [JLOI2009] 二叉树问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
解题思路:用层序遍历的思想求出树的深度和宽度,同时标记每个节点所在的层数,然后用tarjan算法求出a和b的共同祖先,最后套公式输出距离即可。
实现代码:
#include<bits/stdc++.h>
#define int long long
#define endl "\n"
#define fi first
#define se second
#define PII pair<int,int>
using namespace std;
const int N=105;
vector<int> v[N];
vector<int> s[N];
queue<PII> q;
int a,b;
int vis[N],fa[N],c[N];
vector<PII> query[N];
int ans1,ans2;
int k;
PII pa;
void check(int x){
q.push({1,1});
c[1]=1;
int sum=0;
int t=1;
while(!q.empty()){
pa=q.front();
q.pop();
if(pa.se==t) sum++;
else{
ans2=max(ans2,sum);
t++;
sum=1;
}
c[pa.fi]=pa.se;
for(int i=0;i<s[pa.fi].size();++i){
q.push({s[pa.fi][i],pa.se+1});
}
}
ans1=t;
ans2=max(ans2,sum);
}
int find(int x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void tarjan(int x){
vis[x]++;
for(auto i :v[x]){
if(!vis[i]){
tarjan(i);
fa[i]=x;
}
}
for(auto t : query[x]){
int a=t.fi;
if(vis[a]==2){
k=find(a);
break;
}
}
vis[x]++;
}
void solve(){
int n;
cin >> n;
for(int i=1;i<n;++i){
int x,y;
cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
s[x].push_back(y);
}
for(int i=1;i<=n;++i) fa[i]=i;
cin >> a >> b;
query[a].push_back({b,1});
query[b].push_back({a,1});
check(1);
cout << ans1 << endl << ans2 << endl;
tarjan(1);
//for(int i=1;i<=n;++i) cout << c[i] << endl;
cout << (c[a]-c[k])*2+(c[b]-c[k]);
return ;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t=1;
//cin >> t;
while(t--) solve();
return 0;
}