LCA
完了,又忘了一个
LCA(Least Common Ancestors),即最近公共祖先,
是指这样一个问题:在有根树中, 找出某两个结点 u 和 v 最近的公共祖先。
解决这类问题,容易想到一个朴素暴力算法,给出节点 u,v,,首先对 u 进行回溯一直到根节点,并对途中的节点加上标记。
然后对 v 进行回溯,直到找到一个被标记的节点 T, 此时 T 即为 u,v 的 LCA。
此方法写起来很简单但时间复杂度太高,故只适合查询次数极少的时候。 以下为三种效率较高的方法
一、Tarjan 离线算法
Tarjan 算法是一个在图中寻找强连通分量的算法。
算法的基本思想为:任选一结点开始进行深度优先搜索 dfs(若深度优先搜索结束后仍有未访问的结点,则再从中任选一点再次进行)。
搜索过程中已访问的结点不再访问。搜索树的若干子树构成了图的强连通分量。
应用到 LCA 问题上,tarjan 基于并查集,他通过在深搜树的同时,从查询集合中计算公共祖先。
设 lca(u,v),u->v,路径中他们的 lca 就是他们所在支树的根。而深搜顺序是 t->u->t->v。
u 先被访问,u 被访问结束回到 t 的时候,便将 u,t 合并为 1 个集合,那这个时候 u 的祖先就是 t,当访问到 v 的时候,他们的 lca 就是 u 的祖先 t。
void tarjan(int u){ vis[u]=1; fa[u]=u; for(int i=0;i<q[u].size();i++){//枚举 u 的询问 int v=q[u][i]; if(vis[v]) //若(u,v)的 v 访问过,则 uv 不再一个子树,LCA 就是 V 的祖先 cnt[find(v)]++; } for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(vis[v])continue; tarjan(v); fa[v]=u; } }
二、 欧拉序 ST 表
遍历一颗树的同时记录每个节点的访问时间,得到的序列是DFS序。
而欧拉序与dfs序类似,只是在记录时有所不同。
每当访问完一个节点的子树,则需要返回一次该节点,然后记录第一次访问的时间和返回结束时间。
对于点对<u,v>,他们的LCA在u,v的第一次访问序号之间,且是深度最小的点。
于是得到如下算法:
1、 通过dfs遍历树,得到一个欧拉序。计 st[u]表示点u在欧拉序第一次出现的序号, 计 dep[u]表示点 u 的深度。
2、 查询点对<u,v>,即查询[st[u],st[v]]区间深度最小的点对于区间查询最小值可以用 ST 表来完成。
三、 倍增法
求出每个点深度,对于求点对<u,v>的 lca,首先将更深的点向上跳成深度一致,然后 两个点同时向上跳,跳到同一个点就是最近公共祖先。
但是暴力这样做效率过低,考虑通过倍增的思想来完成这一过程。
算法过程:
1) dfs 遍历树,记录每个点的深度以及父亲
2) 设 f[i][j]表示节点 i 的 2j祖先。
3)通过倍增同步跳的方式查询 LCA
#include<bits/stdc++.h> using namespace std; int f[100010][20],vis[100010],dis[100010],n,q,rt; vector<int>g[100010]; void dfs(int x,int d){ vis[x] = 1; dis[x] = d; for(int i = 1;(1<<i)<=dis[x];i++) f[x][i] = f[f[x][i-1]][i-1]; for(int i = 0;i<g[x].size();i++){ int v = g[x][i]; if(vis[v])continue; f[v][0] = x; dfs(v,d+1); } } int lca(int x,int y){ if(dis[x]<dis[y])swap(x,y); int k = dis[x]-dis[y]; for(int i = 0;(1<<i)<=k;i++)//让深度大的点 x 向上跳与 y 保持深度一致 if(k&(1<<i)) x = f[x][i]; if(x==y)return x; for(int i = 19;i>=0;i--){ if(f[x][i]!=f[y][i]) x = f[x][i],y = f[y][i]; } return f[x][0]; } int main(){ scanf("%d",&n); for(int i = 1;i<=n;i++){ int u,v; scanf("%d%d",&u,&v); g[u].push_back(v); g[v].push_back(u); } dfs(1,0); scanf("%d",&q); for(int i = 1;i<=q;i++){ int x,y; scanf("%d%d",&x,&y); int tmp = lca(x,y); printf("%d\n",dis[x]+dis[y]-2*dis[tmp]); } return 0; }
综上所述,我是蒟蒻
2022-10-27 16:23:39
梦与现实间挣扎着,所求为何
你可以借走我的文章,但你借不走我的智慧 虽然我是傻逼本文来自博客园,作者:cztq,转载请注明原文链接:https://www.cnblogs.com/cztq/p/16832281.html