首先是最近公共祖先的概念(什么是最近公共祖先?):
转载:https://www.cnblogs.com/JVxie/p/4854719.html
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点。
换句话说,就是两个点在这棵树上距离最近的公共祖先节点。
所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。
常用的求LCA的算法有:Tarjan/DFS+ST/倍增
什么是Tarjan(离线)算法呢?顾名思义,就是在一次遍历中把所有询问一次性解决,所以其时间复杂度是O(n+q)。
Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解。
下面详细介绍一下Tarjan算法的基本思路:
1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.合并v到u上。
5.寻找与当前点u有询问关系的点v。
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
遍历的话需要用到dfs来遍历至于合并,最优化的方式就是利用并查集来合并两个节点。
伪代码:
Tarjan(u)//marge和find为并查集合并函数和查找函数
{
for each(u,v) //访问所有u子节点v
{
Tarjan(v); //继续往下遍历
marge(u,v); //合并v到u上
标记v被访问过;
}
for each(u,e) //访问所有和u有询问关系的e
{
如果e被访问过;
u,e的最近公共祖先为find(e);
}
}
转载:https://blog.csdn.net/y990041769/article/details/40887469
ST算法:
这个算法是基于RMQ(区间最大最小值编号)的,不懂的可以这里学习一些
而求LCA就是把树通过深搜得到一个序列,然后转化为求区间的最小编号。
比如说给出这样一棵树。
我们通过深搜可以得到这样一个序列:
节点node:1 3 1 2 5 7 5 6 5 2 4 2 1 (先右后左)
深度depth: 1 2 1 2 3 4 3 4 3 2 3 2 1
首位first: 1 4 2 11 5 8 6
那么我们就可以这样写深搜函数
void dfs(int u,int deep)
{
vis[u] = true;node[++tot] = u;first[u] = tot;depth[tot] = deep;
int len = ve[u].size();
for(int i = 0;i < len;++i)
{
int to = ve[u][i].fi;
int value = ve[u][i].se;
if(!vis[to]){
dis[to] = dis[u] + value;
dfs(to,deep + 1);
node[++tot] = u;
depth[tot] = deep;
}
}
}
搜索得到序列之后假如我们想求4 和 7的 LCA
那么我们找4和7在序列中的位置通过first 数组查找发现在6—11
即7 5 6 5 2 4 在上面图上找发现正好是以2为根的子树。而我们只要找到其中一个深度最小的编号就可以了、
这时候我们就用到了RMQ算法。
维护一个dp数组保存其区间深度最小的下标,查找的时候返回就可以了。
比如上面我们找到深度最小的为2点,返回其编号10即可。
st表:https://blog.csdn.net/qq_36386435/article/details/81710309
这部分不会的可以根据上面链接研究一些RMQ
代码可以这样写:
void ST()
{
for(int i = 1;i <= n;++i)
{
dp[i][0] = i;
}
for(int j = 1;(1 << j) <= n;++j)
{
for(int i = 1;i + (1 << j) - 1 <= n;++i)
{
int a = dp[i][j - 1],b = dp[i + (1 << (j - 1))][j - 1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}
int RMQ(int l,int r)
{
int k=(int)(log(double(r-l+1))/log(2.0));
int a = dp[l][k],b = dp[r - (1 << k) + 1][k];
return depth[a] < depth[b] ? a : b;
}
int LCA(int u,int v)
{
int x = first[u],y = first[v];
if(x > y) swap(x,y);
int res = RMQ(x,y);
return node[res];
}
例题解析:
hdu2586
How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 23928 Accepted Submission(s): 9542
Problem Description
There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this “How far is it if I want to go from house A to house B”? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path(“simple” means you can’t visit a place twice) between every two houses. Yout task is to answer all these curious people.
Input
First line is a single integer T(T<=10), indicating the number of test cases.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 40005;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
vector<pair<int,int> >ve[N];
int node[2 * N];
int depth[2 * N];
int first[2 * N];
int dis[2 * N];
bool vis[N];
int tot = 0;
int dp[N][50];
int n,m;
void dfs(int u,int deep)
{
vis[u] = true;node[++tot] = u;first[u] = tot;depth[tot] = deep;
int len = ve[u].size();
for(int i = 0;i < len;++i)
{
int to = ve[u][i].fi;
int value = ve[u][i].se;
if(!vis[to]){
dis[to] = dis[u] + value;
dfs(to,deep + 1);
node[++tot] = u;
depth[tot] = deep;
}
}
}
void ST()
{
for(int i = 1;i <= n;++i)
{
dp[i][0] = i;
}
for(int j = 1;(1 << j) <= n;++j)
{
for(int i = 1;i + (1 << j) - 1 <= n;++i)
{
int a = dp[i][j - 1],b = dp[i + (1 << (j - 1))][j - 1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}
int RMQ(int l,int r)
{
int k=(int)(log(double(r-l+1))/log(2.0));
int a = dp[l][k],b = dp[r - (1 << k) + 1][k];
return depth[a] < depth[b] ? a : b;
}
int LCA(int u,int v)
{
int x = first[u],y = first[v];
if(x > y) swap(x,y);
int res = RMQ(x,y);
return node[res];
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
for(int i = 1;i <= n;++i)
ve[i].clear();
scanf("%d %d",&n,&m);
for(int i = 0;i < n - 1;++i)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
ve[a].pb(mp(b,c));
ve[b].pb(mp(a,c));
}
memset(vis,false,sizeof(vis));
memset(dis,0,sizeof(dis));
tot = 0;
dfs(1,1);
ST();
//cout << 0 << endl;
// for(int i = 1;i <= n;++i)
// {
// printf("%d ",dis[i]);
// }
// printf("\n");
for(int i = 0;i < m;++i)
{
int a,b;
scanf("%d %d",&a,&b);
int res = dis[a] + dis[b] - 2 * dis[LCA(a,b)];
printf("%d\n",res);
}
}
return 0;
}
离线算法:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 40005;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
int b[N];
vector<pair<int,int> >ve[N];
int dis[N];
bool vis[N];
int n,m;
typedef struct Query{
int to,next,id;
}Query;
Query query[810];
int head[N];
int res[N];
int cnt = 0;
void addquery(int u,int v,int id)
{
query[cnt].id = id;
query[cnt].to = v;
query[cnt].next = head[u];
head[u] = cnt++;
}
int Find(int x)
{
int i = x;
while(x != b[x]){
x = b[x];
}
while(i != b[i]){
int t = b[i];
b[i] = x;
i = t;
}
return x;
}
void dfs(int u)
{
int len = ve[u].size();
vis[u] = true;
//cout << u << endl;
for(int i = 0;i < len;++i)
{
int to = ve[u][i].fi;
int value = ve[u][i].se;
if(!vis[to]){
dis[to] = dis[u] + value;
dfs(to);
b[to] = u;
}
}
for(int i = head[u];i != -1;i = query[i].next)
{
//cout << i << endl;
int to = query[i].to;
if(vis[to]){
int x = Find(to);
res[query[i].id] = dis[u] + dis[to] - 2 * dis[x];
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
for(int i = 1;i <= n;++i)
ve[i].clear();
scanf("%d %d",&n,&m);
for(int i = 0;i < n - 1;++i)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
ve[x].pb(mp(y,z));
ve[y].pb(mp(x,z));
}
memset(vis,false,sizeof(vis));
memset(dis,0,sizeof(dis));
memset(head,-1,sizeof(head));
for(int i = 1;i <= n;++i)
b[i] = i;
cnt = 0;
for(int i = 0;i < m;++i)
{
int x,y;
scanf("%d %d",&x,&y);
addquery(x,y,i);
addquery(y,x,i);
}
//cout << 0 << endl;
dfs(1);
//cout << 1 << endl;
for(int i = 0;i < m;++i)
{
printf("%d\n",res[i]);
}
}
return 0;
}