什么是LCA?
这是最近公共祖先问题,就是求出树上两个结点的最近公共祖先。
思考:
显然,可以我们一级一级的去向上搜索,直到遇见它们的最近公共祖先。不过在数据大的情况下,这样显然会T飞了,所以我们要考虑优化。
优化后其实一共有好多种方法,这里只介绍倍增法。
倍增法原理:
倍增法是改变暴力一次只向上找一层的策略,去进行一次向上找 2^i 级的策略,即跳到它的第 2^i 个祖先,这样就可以把 O(n) 的复杂度降到 O(log) 的级别,大幅优化时间复杂度。
如何实现一次跳到它的第 2^i 个祖先?
预处理即可,在dfs整个树的时候预处理出 lca 数组,这样跳的时候就可以直接跳了,lca[x][i] 即指 x 的第 2^i 个祖先。至于如何处理,见代码,自行理解一下就可以了。
lca[x][0]=fa;
for(int i=1;i<=20;i++) lca[x][i]=lca[lca[x][i-1]][i-1];
那究竟如何具体的去跳祖先?
首先我们显然让两个查询的点到达同一深度,否则无法进行后续同步往上跳祖先的活动。代码如下:。
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(dep[lca[x][i]]>=dep[y]) x=lca[x][i];
}
在到达同一深度后,我们再进行同步的往上跳即可,代码如下:
for(int i=20;i>=0;i--)
{
if(lca[x][i]!=lca[y][i])
{
x=lca[x][i];
y=lca[y][i];
}
}
需要注意的是这两次我们都需要从大到小枚举 i 这样才能正确的跳祖先(因为正着枚举显然不成立,这个手模一下即可)。
需要注意的是最后的答案应该是:
lca[x][0]
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
vector<int>a[500005];
int n,m,s,dep[500005],lca[500005][21];
void dfs(int x,int fa)
{
dep[x]=dep[fa]+1;
lca[x][0]=fa;
for(int i=1;i<=20;i++) lca[x][i]=lca[lca[x][i-1]][i-1];
int l=a[x].size();
for(int i=0;i<l;i++)
{
int v=a[x][i];
if(v==fa) continue;
dfs(v,x);
}
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(dep[lca[x][i]]>=dep[y]) x=lca[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
{
if(lca[x][i]!=lca[y][i])
{
x=lca[x][i];
y=lca[y][i];
}
}
return lca[x][0];
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m>>s;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
a[x].push_back(y);
a[y].push_back(x);
}
dfs(s,0);
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
cout<<LCA(x,y)<<endl;
}
return 0;
}
如有错误求大佬指出,蒟蒻感激不尽qaq。