LCA模板
Tarjan属于离线做法,即将问题全部储存起来后一起处理一起回答,相比于即问即答的在线做法,tarjan能仅通过一次DFS就能解决所有的LCA问题。
具体很简单,运用了时间戳和并查集。
用visit数组记录某节点是否访问过,用f记录它的father是谁。
先上伪代码
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); } }
从根节点不断往下遍历,回溯的时候将孩子和父亲合并,当一个节点i的所有儿子都遍历完了(包括儿子的儿子等等),就搜寻一遍询问看看能否回答一些,能够回答就回答,不能回答的之后再回答。
至于能不能回答,取决于这个询问涉及到的另一个点是否已经访问过,如果还没访问,则暂时不能回答,如果访问了,则寻找另一个点的father,该father就是它们的最近公共祖先了。
该算法之所以能够找到LCA,是因为一个已经的访问过的点A和另一个正在访问的点B的LCA一定是一个还没结束访问的点C(就是还没遍历完孩子,没有和C的父亲合并的点)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define N 3000002 5 using namespace std; 6 int n,m,num1,num2,ans[N],f[N],u,v,head[N],qhead[N],visit[N]; 7 struct data2{ 8 int next,to,sign; //链式前向星储存(其实sign可以不用) 9 }line[N],qline[N]; 10 void add1(int u,int v){ 11 num1++; 12 line[num1].next=head[u]; 13 line[num1].to=v; 14 line[num1].sign=num1; 15 head[u]=num1; 16 num1++; 17 line[num1].next=head[v]; 18 line[num1].to=u; 19 line[num1].sign=num1; 20 head[v]=num1; 21 } 22 void add2(int u,int v){ //询问建立双向的链式前向星一方面方便查询询问,另一方面确保能回答到问题 23 num2++; 24 qline[num2].next=qhead[u]; 25 qline[num2].to=v; 26 qline[num2].sign=num2; 27 qhead[u]=num2; 28 num2++; 29 qline[num2].next=qhead[v]; 30 qline[num2].to=u; 31 qline[num2].sign=num2; 32 qhead[v]=num2; 33 } 34 int find(int x){ //找父亲 35 if (f[x]==x) return x; 36 f[x]=find(f[x]); 37 return f[x]; 38 } 39 void tarjan(int u){ //Tarjan求LCA 40 f[u]=u; 41 visit[u]=1; //标记该点访问过 42 int v=0,e=0; 43 for (int i=head[u];i!=0;i=line[i].next){ //遍历孩子 44 v=line[i].to; 45 if (!visit[v]) { //没有访问过的深搜 46 tarjan(v); 47 f[v]=u; 48 } 49 } 50 for (int i=qhead[u];i!=0;i=qline[i].next){ //遍历完该点的孩子后查询询问 51 v=qline[i].to; 52 if (visit[v]){ //若该点访问过,则可以知道答案 53 e=find(v); 54 if (qline[i].sign&1) ans[(qline[i].sign+1)/2]=e; 55 else ans[qline[i].sign/2]=e; 56 } 57 } 58 } 59 int main(){ 60 memset(visit,0,sizeof(visit)); 61 scanf("%d%d",&n,&m); //n个点,m个询问 62 num1=0; 63 num2=0; 64 for (int i=1;i<=n-1;i++){ //建图 65 scanf("%d%d",&u,&v); 66 add1(u,v); 67 } 68 for (int i=1;i<=m;i++){ //建立询问图 69 scanf("%d%d",&u,&v); 70 add2(u,v); 71 } 72 tarjan(1); //默认1为根节点 73 for (int i=1;i<=m;i++) 74 printf("%d\n",ans[i]); 75 return 0; 76 }
思想很简单,要细细品味。
欧拉迹与dfs序类似,不过欧拉迹还记录了该点的出栈。
有三个数组foot deep position,分别记录了欧拉迹,欧拉迹每个位置的点对应的深度和某点首次出现在欧拉迹里的位置。
我们要找某两点的LCA,通过position数组定位到欧拉迹上,他们的LCA必定是深度最小的,所以就是求区间的最小值即可,我们可以用ST优化到O(1)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 #define N 1000001 8 using namespace std; 9 int n,m,p,head[N*2],next[N*2],to[N*2],visit[N],deep[N*2],ji[N*2],num,ti,f[N][100],qwq[N][100]; 10 void add(int u,int v){ 11 num++; 12 next[num]=head[u]; 13 to[num]=v; 14 head[u]=num; 15 num++; 16 next[num]=head[v]; 17 to[num]=u; 18 head[v]=num; 19 } 20 void dfs(int x,int d){ 21 if (!visit[x]) visit[x]=++ti; 22 ji[ti]=x; 23 deep[ti]=d; 24 for (int i=head[x],v;i!=0;i=next[i]){ 25 v=to[i]; 26 if (!visit[v]) { 27 dfs(v,d+1); 28 ji[++ti]=x; 29 deep[ti]=d; 30 } 31 } 32 } 33 void ST(){ 34 int tmp=(int)(log(ti)/log(2)); 35 for (int i=1;i<=ti;i++){ 36 f[i][0]=deep[i]; 37 qwq[i][0]=ji[i]; 38 } 39 for (int j=1;j<=tmp;j++) 40 for (int i=1;i<=ti;i++){ 41 int k=1<<(j-1); 42 if (i-k<=ti) 43 if (f[i][j-1]<f[i+k][j-1]){ 44 f[i][j]=f[i][j-1]; 45 qwq[i][j]=qwq[i][j-1]; 46 } 47 else{ 48 f[i][j]=f[i+k][j-1]; 49 qwq[i][j]=qwq[i+k][j-1]; 50 } 51 } 52 } 53 int lca(int x,int y){ 54 int a=min(visit[x],visit[y]); 55 int b=max(visit[y],visit[x]); 56 int tmp=(int)(log((b-a)+1)/log(2)); 57 if (f[a][tmp]<f[b-(1<<(tmp))+1][tmp]) 58 return qwq[a][tmp]; 59 else return qwq[b-(1<<(tmp))+1][tmp]; 60 } 61 int main(){ 62 scanf("%d%d",&n,&m); 63 for (int i=1,u,v;i<n;i++){ 64 scanf("%d%d",&u,&v); 65 add(u,v); 66 } 67 dfs(1,0); 68 ST(); 69 for (int i=1,u,v;i<=m;i++){ 70 scanf("%d%d",&u,&v); 71 printf("%d\n",lca(u,v)); 72 } 73 return 0; 74 }
倍增也是另一种求LCA的方法。
朴素我们是根据深度让点一步一步地往上跳,知道他们到的点相同。
很显然一步一步太耗时间了,我们可以一次跳好几步。
由二进制拆分可知其步数必能拆成几个2i相加,故用up[i][j]表示i点往上跳2j格后到哪个点。
我们先让深度深的点往上跳,直到两点深度相同或相近时,再让两个点一起跳即可。
最后他们的父亲就是LCA了。
1 #include<cstring> 2 #include<cstdio> 3 #include<iostream> 4 #define N 1000050 5 using namespace std; 6 int n,m,head[N],next[N],to[N],fa[N],up[N][32],deep[N],num,root; 7 void add(int u,int v){ 8 num++; 9 next[num]=head[u]; 10 to[num]=v; 11 head[u]=num; 12 num++; 13 next[num]=head[v]; 14 to[num]=u; 15 head[v]=num; 16 } 17 void dfs(int u){ 18 up[u][0]=fa[u]; 19 for (int i=1;i<=31;i++) 20 up[u][i]=up[up[u][i-1]][i-1]; 21 for (int i=head[u];i;i=next[i]){ 22 int v=to[i]; 23 if (v!=fa[u]){ 24 fa[v]=u; 25 deep[v]=deep[u]+1; 26 dfs(v); 27 } 28 } 29 } 30 int lca(int u,int v){ 31 if (deep[u]<deep[v]) swap(u,v); 32 for (int i=31;i>=0;i--) 33 if (deep[v]<=deep[up[u][i]]) 34 u=up[u][i]; 35 if (v==u) return u; 36 for (int i=31;i>=0;i--) 37 if (up[u][i]!=up[v][i]){ //一起跳 38 u=up[u][i]; 39 v=up[v][i]; 40 } 41 return up[u][0]; 42 } 43 int main(){ 44 scanf("%d%d%d",&n,&m,&root); //root为根 45 memset(fa,127,sizeof(fa)); 46 num=0; 47 for (int i=1,u,v;i<n;i++){ 48 scanf("%d%d",&u,&v); 49 add(u,v); 50 } 51 deep[root]=1;fa[root]=root; 52 dfs(root); 53 for (int i=1,u,v;i<=m;i++){ 54 scanf("%d%d",&u,&v); 55 printf("%d\n",lca(u,v)); 56 } 57 return 0; 58 }