树上最近公共祖先(LCA)
LCA介绍-Lowest Common Ancestor 最近公共祖先
1.何为最近公共祖先?
如图,在一棵无环树上,点4、7的公共祖先有1!5!;最近公共祖先则为5

类似,6、7的最近公共祖先为1;而6、1的LCA则为1.
2.LCA有何用?
当你求出了两个点的LCA,你就可以求出树上这两点间的最短路径.
如上图,4、7的最短路径为 4-5-2-7 .
实现
查阅了一下发现有两种方法:Tarjan(离线)和倍增.
但目前只学过倍增 XD
有关倍增思想后续会专出一篇来讲(ST表也会用到)
在这里先简单解释:参考二进制,任何自然数都可以被拆分成\(2^x + 2^y + 2^z...\),所以我们可以将跳的步数拆分成几个2的乘方之和,便可大大加快程序运行( \(O(nlogn)\) )
注意:从大向小拆!像填二进制数,若能在高位填1就填.
核心思想: 用d(deep)数组记录深度,f[i][j]记录第\(i\)个点的第\(2^j\)个祖先;进行倍增跳时先使两点跳到同一深度,再一起向上跳,直到两点相遇,相遇点即为它们的LCA.
例子(拿刚才的图改了下:P):

8深度3,2深度1
由于\(log_2 n\)不到3,所以8先以\(2^2=4\)的步子向上迈,发现踩到虚空了;
于是再以\(2^1=2\)的步子迈到了5,此时它俩深度一样;

然后一起跳,以\(2^0=1\)的步子跳到同一位置1,则1就是他们的LCA.
下面是代码实现.
Code
1.前置工作
先算\(log_2 n\),限制f数组范围
for(l=0; (1<< (l+1) )<n; l++); //求出理论能跳的最远距离(l=limit)
2.DFS初始化
DFS的同时顺手记录深度,并更新f数组.
void dfs(int now,int fa,int dep) //遍历
{
d[now]=dep; //记录深度
f[now][0]=fa; //初始时f[i][0]就是i的父亲(2^0=1)
for(int j=1;j<=l;j++)
f[now][j]=f[ f[now][j-1] ][j-1]; //其实就是状态转移,更新下now的祖宗们
for(int i=head[now];~i;i=e[i].next) //linklist遍历
{
if(e[i].v!=fa) //防止回头撞墙
dfs(e[i].v,now,dep+1);
}
}
3.跳到同一深度
if(d[a]<d[b]) swap(a,b); //保证a比b深
for(int i=l;i>=0;--i) //从大到小跳!
{
if(d[f[a][i]]>=d[b]) //判断能不能跳(如果跳过头了就不跳)
a=f[a][i];
}
if(a==b) return a; //跳完后在同一节点,说明b就是a的祖先,直接返回
4.一起跳!
for(int i=l;i>=0;--i) //一起跳
{
if(f[a][i]!=f[b][i]) //若祖先不同就 跳!
{
a=f[a][i];
b=f[b][i]; //跳进染缸~
}//因为是祖先不同才跳,所以跳到最后a、b都在同一父亲的子节点处
}
return f[a][0]; //返回a的父亲
例题:洛谷P3379 模版题

浙公网安备 33010602011771号