LCA(最近公共祖先)
1.倍增法(在线算法)
2.Tarjan(离线算法)
注:
在线做法:读一个询问,处理一个,输出一个
离线做法:读完全部询问,再全部处理完,再全部输出
一、倍增法
1、 算法过程:
1.预处理出 fa[i,j] ,和 depth[i] 。( fa[i,j] 表示从i这个点向上跳2j到达的点,depth[i] 表示这个点在这课有根树中的深度,同时要将 depth[0] 设置为0,方便代码的书写)。
2.先将两个点跳到同一个深度。
3.让两个点同时向上跳,直到两个点跳到其最近公共最先的下一层。(如果跳到同一层的话,不能确定是否是最近的)。
4.输出fa[当前点][0]即为最近公共祖先节点。
注:通过递归的方式处理,可以先向上跳2j-1步,在向上跳2j-1步,那么这样的话,预处理是就可以写成fa[i][j]=fa[fa[i][j-1]][j-1]。向上跳的过程有点类似于快速幂,拼凑出一个十进制值等于该节点到根节点距离的二进制数,如果 j 位为1,那么就可以向上跳2j步。
2、时间复杂度
预处理:O(nlogn)
查询:O(logn)
3、模板:
#include <bits/stdc++.h>
using namespace std;
const int N=40010,M=N*2;
int n,m;
struct node{
int to,nex;
}edge[M];
int head[N],cnt;
int depth[N],fa[N][20];
int q[N];
void addedge(int u,int v)
{
edge[cnt].to=v;
edge[cnt].nex=head[u];
head[u]=cnt++;
}
//预处理出depth,和fa数组
void bfs(int root)
{
memset(depth,0x3f,sizeof(depth));
depth[0]=0;depth[root]=1;
queue<int> q;
q.push(root);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(depth[v]>depth[u]+1)
{
depth[v]=depth[u]+1;
q.push(v);
fa[v][0]=u;
for(int k=1;k<=15;k++)
fa[v][k]=fa[fa[v][k-1]][k-1];
}
}
}
}
//向上跳的实现
int lca(int a,int b)
{
if(depth[a]<depth[b]) swap(a,b);
for(int k=15;k>=0;k--)
if(depth[fa[a][k]]>=depth[b])//因为设置了depth[0]=0,所以不会跳过根节点
a=fa[a][k];
if(a==b) return a;
for(int k=15;k>=0;k--)
if(fa[a][k]!=fa[b][k])
{
a=fa[a][k];
b=fa[b][k];
}
return fa[a][0];
}
int main()
{
cin>>n;
int root=0;
memset(head,-1,sizeof(head));
cnt=0;
for(int i=1;i<=n;i++)
{
int u,v;
cin>>u>>v;
if(v==-1) root=u;
else addedge(u,v),addedge(v,u);
}
bfs(root);
cin>>m;
while(m--)
{
int a,b;
cin>>a>>b;
int p=lca(a,b);
if(p==a) cout<<1<<endl;
else if(p==b) cout<<2<<endl;
else cout<<0<<endl;
}
}
二、Tarjan离线算法
1、算法步骤:
主要思想:这里将整个树分为三个部分,并用st数组标记每个点的属于那个部分,st[i]==2,i这个点属于搜索且回溯过的点(绿色部分);st[i]==1,i这个点正在被搜索但未回溯;st[i]==0,i这个点还未被搜索。
通过对st值为2的点进行向上合并,就可以实现将已搜索过的点合并到正在搜索的这条链上,找到最近公共祖先。
1.建立对应的树,将所有问题存入一个结构体或者一个pair数组。
2.进行一个dfs,在回溯的时候利用并查集将节点合并到父结点上。
3.每进行一次搜索,就将问题数组遍历一遍,如果有与当前点相关的问题的时候,判断另一个点是否被搜索且回溯,如果是,则将另一个点的并查集中的值存入存放答案的对应数组位置。
2、时间复杂度:
O(n+m) n个点,m条询问
3、模板
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N=10010,M=N*2;
struct node{
int to,nex,w;
}edge[M];
int head[N],cnt;
int dis[N];
int n,m;
int p[N];
int res[M];
int st[N];
vector<PII> query[N]; // first存查询的另外一个点,second存查询编号
void addedge(int u,int v,int w)
{
edge[cnt].to=v;
edge[cnt].w=w;
edge[cnt].nex=head[u];
head[u]=cnt++;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void bfs(int s)
{
memset(dis,0x3f3f3f3f,sizeof(dis));
queue<int> q;
q.push(s);
dis[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(dis[v]==0x3f3f3f3f)
{
dis[v]=dis[u]+edge[i].w;
q.push(v);
}
}
}
}
void tarjan(int u)
{
st[u] = 1;
for(int i=head[u];i!=-1;i=edge[i].nex)
{
int v=edge[i].to;
if(!st[v])
{
tarjan(v);
p[v]=u;
}
}
for(int i=0;i<query[u].size();i++)
{
PII p=query[u][i];
int y = p.first, id = p.second;
if (st[y] == 2)
{
int anc=find(y);
res[id]=dis[u]+dis[y]-dis[anc]*2;
}
}
st[u] = 2;
}
int main()
{
cin>>n>>m;
memset(head,-1,sizeof(head));
cnt=0;
for(int i=1;i<=n-1;i++)
{
int u,v,w;
cin>>u>>v>>w;
addedge(u,v,w);
addedge(v,u,w);
}
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
if (u!=v)
{
query[u].push_back(PII(v,i));
query[v].push_back(PII(u,i));
}
}
for(int i=1;i<=n;i++)
p[i]=i;
bfs(1);
tarjan(1);
for(int i=1;i<=m;i++)
cout<<res[i]<<endl;
return 0;
}
参考资料:
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/154795/
来源:AcWing算法提高课