关于求LCA三种方法
这里介绍LCA的三种方法(倍增,RMQ,tarjan) ps:以lg3379为例.
1.倍增
可以理解为暴力的优化.先让两个点跳在同一高度,再一起跳2^k次倍祖先.
难点:倍增数组转移方程:bz[i][j]=bz[bz[i][j-1]][j-1]
#include<iostream> #include<cstdio> using namespace std; #define R register #define e exit(0) const int maxn=5e6+10; int n,m,k,s,cnt,head[maxn],fa[maxn],h[maxn],bz[maxn][30]; struct shu{ int to,next; }tree[maxn*2]; inline int fd() { int s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } void add(int from,int to) { tree[++cnt].to=to; tree[cnt].next=head[from]; head[from]=cnt; } void dfs(int x) { for(R int k=head[x];k;k=tree[k].next) { int to=tree[k].to; if(!h[to]) { fa[to]=x; h[to]=h[x]+1; dfs(to); } } } int main() { freopen("s.in","r",stdin); freopen("s.out","w",stdout); n=fd(),m=fd(),s=fd(); for(R int i=1;i<=n-1;++i) { int from=fd(),to=fd(); add(from,to),add(to,from); } h[s]=1,fa[s]=s; dfs(s); for(R int i=1;i<=n;++i) bz[i][0]=fa[i];//为fa[i],不为i,因为2^0==1; for(R int j=1;j<=19;++j) for(R int i=1;i<=n;++i) bz[i][j]=bz[bz[i][j-1]][j-1]; for(R int i=1;i<=m;++i) { int x=fd(),y=fd(); if(h[x]<h[y]) swap(x,y); for(R int k=19;k>=0;--k) if(h[bz[x][k]]>=h[y]) x=bz[x][k]; if(x==y) { printf("%d\n",x); continue; } for(R int k=19;k>=0;--k) if(bz[x][k]!=bz[y][k]) x=bz[x][k],y=bz[y][k]; //如果bz[x][k]==bz[y][k],说明这个点是祖先但不一定是LCA,我们贪心地认为这不是LCA以求最优. //其次这不是一级一级跳,是在原来以跳的2^n次方上再跳2^m次方. printf("%d\n",fa[x]);//bz[x][k]==bz[y][k]就退出,bz[x][k]未赋值给x,bz[y][k]未赋值给y,所以输出bz的又上一级,即fa[x]或fa[y]; } return 0; }
2.RMQ
提前说明这种算法比较慢,且很容易错.写在这里便于对RMQ的理解复习.
大概思路是建树后跑一遍dfs以求dfn,用数组记录点在dfn中的第一次出现的位置.
LCA一定在这两个点的dfn区间内,用RMQ求区间深度最小即LCA.有一点说
明的是在构建RMQ时用数组用同样的方式记录最小值对应的点.
#pragma GCC optimize("Ofast") #pragma GCC optimize("inline") #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define R register const int maxn=5*1e6+10; int n,m,s,cnt,tot,logn[maxn],head[maxn],dfn[maxn],h[maxn],fir[maxn],f[maxn][30],rec[maxn][30]; struct bian{ int to,next; }len[maxn*2]; inline int fd() { int s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } void dfs(int x,int dep) { fir[x]=++tot,dfn[tot]=x,h[tot]=dep; for(R int k=head[x];k;k=len[k].next) { int to=len[k].to; if(!fir[to]) { dfs(to,dep+1); dfn[++tot]=x,h[tot]=dep;//回溯也要记录. } } } int main() { freopen("s.in","r",stdin); freopen("s.out","w",stdout); n=fd(),m=fd(),s=fd(); for(R int i=1;i<n;++i) { int from=fd(),to=fd(); add(from,to),add(to,from); } dfs(s,1); logn[0]=-1; for(R int i=1;i<=tot;++i) f[i][0]=h[i],rec[i][0]=dfn[i],logn[i]=logn[i>>1]+1; for(R int j=1;j<=20;++j) for(R int i=1;i+(1<<j)-1<=tot;++i) { if(f[i][j-1]<f[i+(1<<j-1)][j-1]) { f[i][j]=f[i][j-1]; rec[i][j]=rec[i][j-1]; } else{ f[i][j]=f[i+(1<<j-1)][j-1]; rec[i][j]=rec[i+(1<<j-1)][j-1];//跟着变化记录点. } } for(R int i=1;i<=m;++i) { int x=fd(),y=fd(); int l=fir[x],r=fir[y]; if(l>r) swap(l,r); int num=logn[r-l+1]; if(f[l][num]<f[r-(1<<num)+1][num]) printf("%d\n",rec[l][num]); else printf("%d\n",rec[r-(1<<num)+1][num]); } return 0; }
3.tarjan
提前说明为离线算法,复杂度O(n+q),较快.ps:q为询问次数.
大概思路为构建已经存在的树与询问树.先dfs遍历已经存在的树,
再从遍历的节点中寻找询问树中与其关联的节点.看询问树中的节点是否
遍历过,遍历过就一定走过了LCA,用并查集维护最开始的爸爸则是LCA.
其中疑问,最开始的爸爸不是根结点吗?不,根节点的儿子还未遍历回去,还未认这个爸爸.
以此类推,存在树中的所有子树结构均满足这中关系.
#include<iostream> #include<cstdio> using namespace std; #define R register const int maxn=1e6+10; int n,m,s,cnt,qcnt,fa[maxn],head[maxn],qhead[maxn],vis[maxn],lca[maxn*2]; struct bian{ int to,next; }len[maxn*2]; struct qbian{ int to,next; }qlen[maxn*2]; inline int fd() { int s=1,t=0; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') s=-1; c=getchar(); } while(c>='0'&&c<='9') { t=t*10+c-'0'; c=getchar(); } return s*t; } void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } void qadd(int from,int to) { qlen[++qcnt].to=to; qlen[qcnt].next=qhead[from]; qhead[from]=qcnt; } int find(int x) { if(x==fa[x]) return x; else return fa[x]=find(fa[x]); } void tarjan(int x) { vis[x]=1; for(R int k=head[x];k;k=len[k].next) { int to=len[k].to; if(vis[to]) continue; tarjan(to); fa[to]=x;//未回溯,未认father. } for(R int k=qhead[x];k;k=qlen[k].next) { int to=qlen[k].to; if(vis[to]) { lca[k]=find(to); if(k%2) lca[k+1]=lca[k]; else lca[k-1]=lca[k]; //两个为一组. } } } int main() { freopen("s.in","r",stdin); freopen("s.out","w",stdout); n=fd(),m=fd(),s=fd(); for(R int i=1;i<=n;++i) fa[i]=i; for(R int i=1;i<n;++i) { int from=fd(),to=fd(); add(from,to),add(to,from); } for(R int i=1;i<=m;++i) { int from=fd(),to=fd(); qadd(from,to),qadd(to,from); } tarjan(s); for(R int i=1;i<=m;++i) printf("%d\n",lca[i*2]); return 0; }