lca,即最近公共祖先.
最近公共祖先的概念是在一棵树上的,就像下面这棵树:
这棵树里,3和6的最近公共祖先就是1,3和2的最近公共祖先就是2.
也就是说,两个点i和j的最近公共祖先是连接它们的最短路径上深度最小的节点.
所以,我们有一个最坏时间复杂度为O(n)的算法求lca.
我们先找到深度更深的节点,然后追溯它的父亲直至它的祖先k与另一节点的深度相同.
接下来,我们同时追溯两个节点的父亲,直至这两个父亲是同一个节点.
那么这个节点就是lca.
代码如下:
void dfs(int k=1){ if (k==1) d[k]=1,father[k]=0; for (int i=lin[k];i;i=e[i].next) if (e[i].y!=father[k]){ father[e[i].y]=k; d[e[i].y]=d[k]+1; dfs(e[i].y); } } //预处理 inline int query(int x,int y){ if (d[x]<d[y]) swap(x,y); while (d[x]!=d[y]) x=father[x]; while (x!=y) x=father[x],y=father[y]; return x; } //查询
那么这串代码一般来说比较快,但是有个恶心数据给你出了个链状树,这就会退化成O(n).
所以,我们还有更稳定的算法.
主要有三种算法能够较快的求出lca:倍增,RMQ,tarjan.
这里介绍倍增,是一种在线算法.
倍增的预处理是O(nlog(n)),查询O(log(n)).
那么它的思想是倍增,grand[i][k]记录的是i节点的2^k倍祖先.(这里将父亲定义为1倍祖先,爷爷作为2倍祖先,依次类推).
那么预处理就只需要多预处理一个grand数组.
跟ST算法很像,大概是这样的:
void dfs(int k,int f){ d[k]=d[f]+1; grand[k][0]=f; for (int i=1;(1<<i)<=d[k];i++) grand[k][i]=grand[grand[k][i-1]][i-1]; for (int i=lin[k];i;i=e[i].next) if (e[i].y!=f) dfs(e[i].y,k); } //预处理
那么查询进行同样的操作,先是到达同一深度.
到达同意深度,我们可以先算这两个点的高度差.
然后用高度差的二进制为1的位i都跳到grand[x][i].
大概代码是这样的:
if (d[x]<d[y]) swap(x,y); int c=d[x]-d[y]; //算出高度差 for (int k=0;(1<<k)<=c;k++) //只要这一位在最高位之后 if (c&(1<<k)) x=grand[x][k]; //这一位为1,往上跳
那么同时向上跳.
那么倍增同时向上跳是需要注意,只有grand[x][k]和grand[y][k]不等我们才跳.
因为若他们相等了,不一定是最近的.
最后跳完了,还要判断一下相不相等,在选择返回x和father[x].
像这样:
for (c=19;c>-1;c--) if (grand[x][c]!=grand[y][c]) x=grand[x][c],y=grand[y][c]; //不等,往上跳 if (x==y) return x; else return grand[x][0];
那么完整的查询代码如下:
inline int query(int x,int y){ if (d[x]<d[y]) swap(x,y); int c=d[x]-d[y]; //算出高度差 for (int k=0;(1<<k)<=c;k++) //只要这一位在最高位之后 if (c&(1<<k)) x=grand[x][k]; //这一位为1,往上跳 for (c=19;c>-1;c--) if (grand[x][c]!=grand[y][c]) x=grand[x][c],y=grand[y][c]; //不等,往上跳 if (x==y) return x; else return grand[x][0]; } //查询
好,那么这样就差不多了.
至于另外两个算法,我以后学了再写.