最近公共祖先?!
有人肯定要问:什么是最近公共祖先???!!
好那我们现在就来说说什么是最近公共祖先吧!
最近公共祖先有一个好听的名字叫——lca
这是一种算法,这个算法基于并查集和深度优先搜索。算法从根开始,对每一棵子树进行深度优先搜索,访问根时,将创建由根结点构建的集合,然后对以他的孩子结点为根的子树进行搜索,使对于 u, v 属于其某一棵子树的 LCA 询问完成。这时将其所有子树结点与根结点合并为一个集合。 对于属于这个集合的结点 u, v 其 LCA 必定是根结点。
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
怎么求两个已知点的LCA呢·?
有一个比较暴力的想法:先将这两个点的路径上的所经过的所有点,然后再从根节点向下找第一个分叉的点,这个点就是这两个点的最近公共祖先。
还有一个想法:先将两个深度不同的点转化成深度相同的点,然后再将这两个点一起向上跳,直到找到同一个点。
§ one。倍增法
何为倍增法?
倍增法就是我们先把深度不同的两个点转化成深度相同的点。然后再对这两个点同时倍增。
这种做法我们先用一个数组fa[x]【y】数组来存第x个节点的2^y的父亲节点。
这样我们就能在o(lg n)的时间内查询任意一个点的lca。
所以我们还是采用上面所述的那种做法,现将深度不同的两个点转化成深度相同的两个点。
然后再对两个点同时进行倍增。
好那我们下面来求一求给定两点:x,y的最近公共祖先吧!
代码1:
#include<vector> #include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 500001 #define maxn 123456 using namespace std; vector<int>vec[N]; int n,x,y,fa[N][20],deep[N],m,root; void dfs(int x) { deep[x]=deep[fa[x][0]]+1; for(int i=0;fa[x][i];i++) fa[x][i+1]=fa[fa[x][i]][i]; for(int i=0;i<vec[x].size();i++) { if(!deep[vec[x][i]]) { fa[vec[x][i]][0]=x; dfs(vec[x][i]); } } } int lca(int x,int y) { if(deep[x]>deep[y]) swap(x,y);//省下后面进行分类讨论,比较方便 for(int i=18;i>=0;i--) { if(deep[fa[y][i]]>=deep[x]) y=fa[y][i];//让一个点进行倍增,直到这两个点的深度相同 } if(x==y) return x;//判断两个点在一条链上的情况 for(int i=18;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { y=fa[y][i]; x=fa[x][i]; } } return fa[x][0];//这样两点的父亲就是他们的最近公共祖先 } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } deep[root]=1; dfs(root); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
§ two。树剖法。
看到这个算法,肯定有想问树剖法是个什么鬼?
树抛嘛,顾名思义肯定是将一棵树进行剖分。
树链抛分的核心是划分一棵树的重边和轻边。
我们把一个节点所拥有的子节点的个数记为size
对于一棵树来说,一个节点u和他的父节点v之间一定只有一条重边。这条重边连向她的儿子中size值最大的节点。
这样一棵树的所有重边就组成了一条重链。
对于节点x我们记录x的重边的顶点top x.
加入一个节点到另一条节点有轻边,那这条轻边满足:2*size u<size V
因此,根到一棵树的节点上最多有long n 条轻边
现在我们要用树链剖分来求两节点x,y的lca
首先我们要先判断两个节点的top那个大
我们把top值小的节点改成fa[top[x]]
重复上述两个过程直到top[x]=top[y]
最后将上述两个节点中深度较小的值作为这两点的lca。
下面给出本人代码
#include<vector> #include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 500001 #define maxn 123456 using namespace std; vector<int>vec[N]; int n,m,root,x,y,fa[N],deep[N],size[N],top[N]; int lca(int x,int y) { for( ;top[x]!=top[y];) { if(deep[top[x]]<deep[top[y]]) swap(x,y); x=fa[x]; } if(deep[x]>deep[y]) swap(x,y); return x; } void dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=0;i<vec[x].size();i++) { if(fa[x]!=vec[x][i]) { fa[vec[x][i]]=x; dfs(vec[x][i]); size[x]+=size[vec[x][i]]; } } } void dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t]) t=vec[x][i]; if(t) { top[t]=top[x]; dfs1(t); } for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&vec[x][i]!=t) dfs1(vec[x][i]); } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } dfs(root); dfs1(root); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
§ three。并查集
对于一棵树的每个节点父亲的查询问题,我们可以采用并查集的方法。
我们先用一个数组fa[x]来储存x的父亲节点,每个点与他的父亲节点在一个并查集里。
如果一个点满足:fa[x]=x,则说明x是这棵树的根节点。
我们在查询两个节点是否在一个并查集中时,只要查询这两个节点的所在子树根节点是否相同即可。
在合并两个字数的集合时,我们只要讲fa[root[x]]变成root[x]即可。
有没有感觉这个方法教前面的几个方法来说要简单很多?
好,既然这么简单,我们就直接上代码吧!
#include<vector> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 10000 using namespace std; int n,m,t,fa[N],x,y; int find(int x) { return fa[x]==x?x:fa[x]=find(x); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { scanf("%d%d%d",&t,&x,&y); if(t==1) fa[find(x)]=find(y); else { if(find(x)==find(y)) printf("YES"); else printf("NO"); } } return 0; }
§ four。tarjian法(简称塔尖)
与之前的树抛和倍增法不同,tarjian算是一种离线算法。
我们需要将米一组询问用vec储存下来,将其挂在改组询问询问的两个节点上。
之后遍历整棵树。在访问一个节点x时,我们设置这个节点的fa【x】=x;只有在询问完时,我们将这个点的fa【x】设置城dad【x】。
之后我们在询问一个节点时,枚举过于这个点的所有询问。若果询问中的另一个节点已经被访问过,那么这两个点的lca就是已经被访问过的这个点。
代码
#include<vector> #include<stdio.h> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 500001 using namespace std; vector<int>vec[N],que[N]; int n,m,qx[N],qy[N],x,y,root,fa[N],dad[N],ans[N]; int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void dfs(int x) { fa[x]=x; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=dad[x]) dad[vec[x][i]]=x,dfs(vec[x][i]); for(int i=0;i<que[x].size();i++) if(dad[y=qx[que[x][i]]^qy[que[x][i]]^x]) ans[que[x][i]]=find(y); fa[x]=dad[x]; } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } for(int i=1;i<=m;i++) { scanf("%d%d",&qx[i],&qy[i]); que[qx[i]].push_back(i); que[qy[i]].push_back(i); } dfs(root); for(int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
来源:https://www.cnblogs.com/z360/p/6822658.html