讲讲离线的做法,思想比较容易理解,代码也容易读实现,最近公共祖先顾名思义就是树上两个结点的公共祖先节点中层数最深的那个,有向树的树形是固定的,任意两点的LCA也是相同的,而若是无根树的话,以任意结点为根建树都会获得不同的情况,从而两点的lca也会不同,LCA一个简单应用就是求树上两点之间的最短距离,其值就是dis[a]+dis[b]-2*dis[lca(a,b)];
LCA的离线算法主要就是依靠并查集和dfs实现,先记录需要查询的结点对x,y,当dfs到需要查询的结点x时,若另一个结点y已经dfs过了,说明另一个结点y要嘛是x的祖先节点,要嘛是在他们的lca的另一棵子树中,每次一颗子树dfs完成后,就把子树的树根的并查集父节点设置为其父节点,所以对于这个子树的所有结点查询并查集祖先节点都是该树根的父节点。
如图,dfs每次都从树枝开始dfs,如果我们要查lca(6,10),那么让我们dfs到10时,结点6已经访问过了,因为以6为根的子树已经完全dfs完成,所以6,15,7结点查询并查集祖先都是4,lca(6,10)=F(6)=4;而如果我们要查询lca(6,1),当我们遍历到结点1时,以4为根的子树已经全部访问过了,所以对6查询并查集祖先F(6)就等于8了,所以lca(6,1)=F(6)=8;这就是lca离线的做法。具体实现见例题代码。
一般情况离线做法都是用另一个vector 数组来储存lca查询请求的。
Nearest Common Ancestors POJ - 1330
有向树找LCA,需要统计入度来找根,从根开始dfs并处理lca查询。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1005;
const int maxn = 40005;
int n, m ;
vector<int> e[maxn];
//vector<pair<int,int> > query[maxn];
//int dis[maxn];
bool vis[maxn];
int f[maxn];
int temp[maxn];
int beg, en;
int ans;
int F(int x){
if (f[x] == x)return x;
return f[x] = F(f[x]);
}
void findans(int cnt){
vis[cnt] = 1;
if (cnt == beg){ if (vis[en]){ ans = F(en); return; } }
if (cnt == en){ if (vis[beg]){ ans = F(beg); return; } }
for (int i = 0; i < e[cnt].size(); i++){
int v = e[cnt][i];
if (!vis[v]){
findans(v);
f[v] = cnt;
}
}
}
int main(){
int t;
int x, y, z;
scanf("%d", &t);
while (t--){
scanf("%d", &n);
memset(temp, 0, sizeof(temp));
for (int i = 1; i <= n; i++){ f[i] = i; e[i].clear(); }
for (int i = 1; i < n; i++){
scanf("%d%d", &x, &y);
e[x].push_back(y);
temp[y]++;
}
scanf("%d%d", &beg, &en);
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++){
if (temp[i] == 0){ findans(i); break; }
}
printf("%d\n", ans);
}
return 0;
}
How far away ? HDU - 2586
两点的最近距离,dfs 的过程中同时处理出每点离根结点的距离,因为是无向图,以任意结点为根转有向树都行。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1005;
const int maxn = 40005;
int n, m ;
vector<pair<int,int> > e[maxn];
vector<pair<int,int> > query[maxn];
int ans[maxn][3];
int dis[maxn];
bool vis[maxn];
int f[maxn];
int F(int x){
if (f[x] == x)return x;
return f[x] = F(f[x]);
}
void findans(int cnt,int d){
dis[cnt] = d;
vis[cnt] = 1;
for (int i = 0; i < query[cnt].size(); i++){
pair<int,int> v = query[cnt][i];
if (vis[v.first]){
ans[v.second][2] = F(v.first);
}
}
for (int i = 0; i < e[cnt].size(); i++){
pair<int,int> v = e[cnt][i];
if (!vis[v.first]){
findans(v.first, d + v.second);
f[v.first] = cnt;
}
}
}
int main(){
int t;
int x, y, z;
scanf("%d", &t);
while (t--){
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++){ f[i] = i; e[i].clear(); }
for (int i = 1; i < n; i++){
scanf("%d%d%d", &x, &y, &z);
e[x].push_back({ y, z });
e[y].push_back({ x, z });
}
for (int i = 0; i < m; i++){
scanf("%d%d", &x, &y);
query[x].push_back({ y, i });
query[y].push_back({ x, i });
ans[i][0] = x;
ans[i][1] = y;
}
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof dis);
findans(1, 0);
for (int i = 0; i < m; i++){
printf("%d\n", dis[ans[i][0]] + dis[ans[i][1]] - 2 * dis[ans[i][2]]);
}
}
return 0;
}