给定一个定义:x,y的最近公共祖先称之为LCA(x,y)是x和y的所有公共祖先中深度最大的一个节点
我们总结一下它的三种求法:
第一种是最简单的不断向上标记节点,如果x的标记节点和y的标记节点相遇了,就找到了LCA(x,y),复杂度为O(n)
第二种是树上倍增法,其原理是倍增(类比于ST表)
在树上倍增的过程类似于一个动态规划的过程,我们约定F[x,y]表示x的2^k辈祖先,为了简化处理,我们将默认0作为节点不存在,也即是F[x,k]=0。
F[x,0]为x的父辈,F[x,1]为x的父辈的父辈也就是祖父辈,F[x,1]=F[F[x,0],0]。我们将整个过程的一般化,也就是F[x,k]=F[F[x,k-1],k-1](也就是他的2^k辈祖先就是2^(k-1)辈祖先的2^(k-1)辈祖先)k∈[1,log2n]
首先进行预处理,将每个节点的父辈以及父辈的父辈以及父辈的父辈的父辈的父辈...都处理出来
(注意BFS是单调的,每次处理完一个层次,再处理下一个层次,也就是F处理[x,k]时,F[x-1,k]已经被处理完成了,另外就是如果他的祖先不存在,实际上是F[0,k]永远是0,简化处理过程)
- HDU-2586 How far away?
#include <iostream> #include <cstdio> #include <cmath> #include <queue> #include <cstring> #define fore(i,a,b) for(int i=a;i<=b;i++) #define fort(i,a,b) for(int i=a;i>=b;i--) #define forg(i,x) for(int i=head[x];i;i=next[i]) #define mem0(i) memset(i,0,sizeof(i)) #define meme(i,a) memset(i,a,sizeof(i)) const int MAXN=50010; int T, n, m, tot, t; int f[MAXN][20], d[MAXN], dist[MAXN]; int ver[MAXN*2], next[MAXN*2], edge[MAXN*2], head[MAXN]; std::queue<int>q; void add(int x,int y,int z){ ver[++tot]=y, edge[tot]=z, next[tot]=head[x], head[x]=tot; } void bfs(){ q.push(1); d[1]=1; while(!q.empty()){ int x=q.front();q.pop(); forg(i,x){ int y=ver[i]; if(d[y]) continue; d[y]=d[x]+1; dist[y]=dist[x]+edge[i]; f[y][0]=x; fore(j,1,t) f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } } int lca(int x,int y){ if(d[x]>d[y]) std::swap(x,y); fort(i,t,0) if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; fort(i,t,0) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int main(){ std::cin>>T; while(T--){ std::cin>>n>>m; t=(int)(log(n)/log(2))+1; fore(i,1,n) head[i]=d[i]=0; tot=0; fore(i,1,n-1){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } bfs(); fore(i,1,m){ int x,y; scanf("%d%d",&x,&y); printf("%d\n",dist[x]+dist[y]-2*dist[lca(x,y)]); } } return 0; }
第三种方法是利用并查集和深度优先搜索在遍历的基础上进行更新答案,但是问题就是必须离线回答问题,时间复杂度为O(n+m)
#include <iostream> #include <cstdio> #include <cmath> #include <queue> #include <cstring> #include <vector> #define fore(i,a,b) for(int i=a;i<=b;i++) #define fort(i,a,b) for(int i=a;i>=b;i--) #define mem0(i) memset(i,0,sizeof(i)) #define meme(i,a) memset(i,a,sizeof(i)) const int MAXN=50010; const int INF=1<<30; int T, n, m, tot, t; int ver[MAXN*2], next[MAXN*2], edge[MAXN*2], head[MAXN]; int fa[MAXN], d[MAXN], vis[MAXN], lca[MAXN], ans[MAXN]; std::vector<int> query[MAXN],query_id[MAXN]; void add(int x,int y,int z){ ver[++tot]=y, edge[tot]=z, next[tot]=head[x], head[x]=tot; } void add_query(int x,int y,int id){ query[x].push_back(y),query_id[x].push_back(id); query[y].push_back(x),query_id[y].push_back(id); } int get(int x){ if(fa[x]==x) return x; return fa[x]=get(fa[x]); } void tarjan(int x){ vis[x]=1; for (int i = head[x]; i; i = next[i]) { int y=ver[i]; if(vis[y]) continue; d[y]=d[x]+edge[i]; tarjan(y); fa[y]=x; } for(int i=0;i<query[x].size();i++) { int y=query[x][i],id=query_id[x][i]; if(vis[y]==2){ int lca=get(y); ans[id]=std::min(ans[id],d[x]+d[y]-2*d[lca]); } } vis[x]=2; } int main(){ std::cin>>T; while(T--){ std::cin>>n>>m; fore(i,1,n){ head[i]=0,vis[i]=0; fa[i]=i; query[i].clear(); query_id[i].clear(); } tot=0; fore(i,1,n-1){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } fore(i,1,m){ int x,y; scanf("%d%d",&x,&y); if(x==y) ans[i]=0; else{ add_query(x,y,i); ans[i]=INF; } } tarjan(1); fore(i,1,m) printf("%d\n",ans[i]); } return 0; }