定义
LCA,最近公共祖先,是指一棵树上两个节点的深度最大的公共祖先。也可以理解为两个节点之间的路径上深度最小的点。
我们这里用了倍增的方法求了LCA。
我们的基本的思路就是,用dfs遍历求出所有点的深度。father[i][j]数组用来求的是距离节点i,距离
2j
的祖先。可以知道,father[i][0]就是它的直接父亲。然后通过倍增的思路求出father数组的所有元素。然后进行lca。求lca的基本思路是:先让深度较大的点向上跳,
然后x和y再同时向上跳2的幂,总会跳到这样两个点,他们的父亲结点是同一个点,那就是x和y的LCA。
code
首先我们需要用邻接表建一颗参天大树~
namespace graph
{
int n,m,s,top=0;
int head[maxn];
struct Hy
{
int dot_order,next_location;
}a[maxn*2];
//邻接表
void insert_edge(int x,int y)
{
top++;
a[top].dot_order=y;
a[top].next_location=head[x];
head[x]=top;
}
//插入一条x到y的边
void init()
{
n=read();
m=read();
s=read();
//这里n是点数,m是边数,s是根节点编号
for(int i=1;i<=n-1;i++)
{
int x,y;
x=read();
y=read();
insert_edge(x,y);
insert_edge(y,x);
}
}
}using namespace graph;
//建图建树基本操作
然后开始重头戏——倍增。
int depth[maxn],father[maxn][64];
/*depth数组用来记录当前点的深度
father[i][j]代表距离i 2^j的祖先
*/
dfs函数求深度和直接父亲:
void hy(int u)
{
for(int i=head[u];i;i=a[i].next_location)
//遍历邻接表
{
int to=a[i].dot_order;
if(to==father[u][0])continue;
//如果to是u的父亲,那么就说明这条边被访问过了,不能再回溯了
depth[to]=depth[u]+1;
//更新深度
father[to][0]=u;
//更新父亲结点
hy(to);
//继续深度优先遍历
}
}
这个时候我们已经求出了所有的
father[i][0]
,我们需要计算
i
<script id="MathJax-Element-31" type="math/tex">i</script>的其他祖先。
预处理father数组:
void father_chuli()
{
for(int j=1;(1<<j)<=n;j++)
{
for(int i=1;i<=n;i++)
father[i][j]=father[father[i][j-1]][j-1];
/*有点类似RMQ问题啊。。。
i的2^(j-1)级祖先的2^(j-1)级祖先就是
I的2^I级祖先。
*/
}`
}
好了,现在所有的预处理工作都完成了。我们开始随便搞~
int lca(int a,int b)
{
if(depth[a]>depth[b])swap(a,b);
//让b的深度更深
int f=depth[b]-depth[a];
//f是b和a的深度之差
for(int i=0;(1<<i)<=f;i++)
{
if((1<<i)&f)b=father[b][i];
}//让b跳到跟a相同高度上
if(a!=b)/*如果a和b不是同一个结点那么就要继续跳
如果是同一个结点,那么它就是LCA
*/
{
for(int i=(int)log2(n);i>=0;i--)
//从大到小跳。正确性显然。
{
if(father[a][i]!=father[b][i])
//如果不相等,就说明该节点的深度还是比LCA大
{
a=father[a][i];
b=father[b][i];
//那就继续跳
}
}
a=father[a][0];
//这个时候a和b还不是同一个节点,但是a和b的父亲就是x和y的lca。
}
return a;
}