普通的介绍
在我们小学二年级学习过的《算法导论》第三版 第337页上,有一个思考题(什!这么重要的内容只是思考题吗!)
介绍的就是Tarjan 的脱机最小公共祖先
(Tarjan’s Offline Least-Common-Ancestors Problem)
什么是LCA
LCA,就是 Least-Common-Ancestors ,指在一棵树中,两个节点的最近的公共祖先。
在这棵以E为根的树中,A节点和B节点的最近公共祖先为节点C。而节点D和节点E虽然也为A与B节点的祖先,但是不是最近的祖先。
为什么我需要Tarjan算法求LCA?
在一个N个节点的树中
若我们使用最暴力的普通算法
(即算出两个点的深度,深度大的那个点往上访问,直到两个点汇合)
易得Q次查询的时间复杂度为O(NQ)
但是如果我们使用Tarjan算法
Q次查询的时间复杂度为O(N+Q)!这是个不小的改进!
若执行过一次该算法,便可以求出所有点对之前的LCA!
(但是需要O(N2)空间来存储,或者我们使用vector或者数组存储就可以O(N)存储!)
原理ADT
Tarjan 的 LCA的ADT如下:
LCA(node){
anc[find_fa(node)] = node;
for(auto &it : G[node]){
LCA(it)
add(node,it)
anc[find_fa(node)] = node
}
flag[node] = 1
for(auto &it : Q[node])
if(flag[it])
print ("node 和 it 的 LCA 为:" anc[find_fa(it)])
}
读者应该发现了,该核心ADT主要用到了并查集(Union Find)的内容!
(并查集代码建议使用启发式策略,rank比较与路径压缩,可以使得并查集部分的时间复杂度更加优秀)
- 首先代码第二行,把这个节点的代表的anc祖先设置为他本身
- 遍历他的每一个孩子,递归LCA(每个node的孩子)
- 并查集,把node和他的孩子并起来,把这个节点的代表的anc祖先再次设置为他本身
- 孩子遍历完后,该节点的flag设置为1(表示该节点的所有孩子都遍历完了,即该节点已经访问完毕)
- 遍历所有查询,如果查询里面需要询问node 和 it 的祖先 , 并且it 节点也访问过的话,输出 it所在集合的anc祖先
容易证明,对于每对不同的点对,if(flag[it])只会运行一次。
理性的证明这里没有(笔者太菜了!)
感性的证明:并查集后,所有直接儿子的LCA都设置成为了root,因此询问结果总是正确的(?)
模板题目
AC模板代码
照着算法导论的十二行ADT,手写并查集,vector搞一搞,AC!
///时间复杂度 O(N+Q)
#include <bits/stdc++.h>
#define show(x) std::cerr << #x << "=" << x << std::endl
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
typedef long long ll;
using namespace std;
const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
vector<int>G[MAX];
vector<pair<int,int> >Q[MAX];
int anc[MAX],fa[MAX],in[MAX],flag[MAX],rk[MAX];
int root;
int ans[MAX];
void init(int n,int m){
memset(anc,0,sizeof(anc));
memset(flag,0,sizeof(flag));
memset(rk,0,sizeof(rk));
memset(in,0,sizeof(in));
for(int i=0;i<=n;++i){
fa[i] = i;
G[i].clear();
Q[i].clear();
}
for(int i=1;i<n;++i){
int x,y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
for(int i=1;i<=m;++i){
int ta,tb;cin >> ta >> tb;
Q[ta].push_back(make_pair(tb,i));
Q[tb].push_back(make_pair(ta,i));
}
}
int find_fa(int x){
if(x==fa[x])return x;
return fa[x] = find_fa(fa[x]);
}
void add(int x,int y){
int fx = find_fa(x);
int fy = find_fa(y);
if(rk[fx] > rk[fy])fa[fy] = fx;
else fa[fx] = fy;
if(rk[fx] == rk[fy])rk[fy]++;
}
void LCA(int node,int fa){
for(auto &it : G[node]){
if(it == fa)continue;
LCA(it,node);
add(node,it);
anc[find_fa(node)] = node;
}
flag[node] = 1;
for(auto &it : Q[node]){
if(flag[it.first]){
ans[it.second] = anc[find_fa(it.first)];
}
}
}
void ANS(int m){
for(int i=1;i<=m;++i){
cout << ans[i];
if(i!=m)cout << endl;
}
}
int main()
{
IOS;
int n,m,s;
cin >> n >> m >> s;
init(n,m);
LCA(s,s);
ANS(m);
return 0;
}