利用LCA求解树上任意两点距离
u和v的之间的距离为u到树根上的距离加上v到树根上的距离减去2×lca到树根的距离
即
一、暴力搜索法
时间复杂度均为O(n)
二、树上倍增法
在树上构造ST表
表示
节点向上走
步到达的节点。
其中为
父节点
递推公式 ,i=1,2,……n, j=0,1,2,……k,
即走步分为两步
采用同步前进法的思想,但利用倍增的走法。
1、使y到与x同深度的节点
2、x,y同时向上跳
k从大到小遍历,若f[x][i]和f[y][i]相同,说明跳的过高,高于lca,不进行任何操作;
如果不相同,则将x,y上跳至该位置。直到k=0结束后,当前x,y所指节点的父节点即是其lca
时间复杂度:构造ST表需要,单词查询需要
模板代码
void dfs(int x)
{
d[x]=d[f[x][0]]+1;//深度从0开始存
for(int i=1;(1<<i)<=d[x];i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y=f[x][0]) continue;
f[y][0]=x;
dfs(y);
}
}
int lca(int x,int y)
{
if(d[x]>d[y]) swap(x,y);
while(d[x]<d[y])
{
int k=lg[d[y]-d[x]];
y=f[y][k];
}//y上跳至与x同深度
if(x==y) return x;
int k=lg[d[x]];
for(i=k;~i;i--)
{
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
}
return f[x][0];
}
三、在线RMQ算法
欧拉序列是指在深度遍历过程中把依次经过的节点记录下来,把回溯时经过的节点也记录下来,一个节点可能被记录多次,相当于从树根开始,一笔画出一个经过所有节点的回路
两个节点的LCA一定是两个节点之间的欧拉序列中深度最小的节点。
获得一棵树的欧拉序列,以u,v首次出现的下标为区间端点,利用RMQ查询区间中节点深度的最小值,该节点即为u,v的LCA
void dfs(int x,int d)
{
vis[x]=true;
pos[x]=++tot;//首次出现位置,向下搜索时记录
seq[tot]=x;//欧拉序列
dep[x]=d;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(vis[y]) continue;
dis[y]=dis[x]+edge[i];
dfs(y,d+1);
seq[++tot]=x;//回溯时再次标记
dep[tot]=d;
}
}
注意点:F里存储的为欧拉序列的下标,不是深度
void ST_create()
{
for(int i=1;i<=cnt;i++) f[i][0]=i;//f里存欧拉序列下标,不存深度
int k=log2(cnt);
for(int j=1;j<=k;j++)
for(int i=1;i<=cnt-(1<<j)+1;i++)
if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]])
f[i][j]=f[i][j-1];
else f[i][j]=f[i+(1<<(j-1))][j-1];
}
int RMQ(int l,int r)
{
int k=log2(r-l+1);
if(dep[f[l][k]]<dep[f[r-(1<<k)+1][k]])
return f[l][k];
else return f[r-(1<<k)+1][k];
}
int lca(int x,int y)
{
int l=pos[x],r=pos[y];
if(l>r) swap(l,r);
return seq[RMQ(l,r)];
}
时间复杂度:
构造ST表需要,单次查询需要
四、离线Tarjan算法
利用并查集的特性来找lca
操作:
1、初始化集合号数组和访问数组,fa[i]=i,vis[i]=0;
2、从根节点出发深度优先遍历,回溯时记录父子节点关系。
3、当某个节点的邻接点全部遍历完毕时,此时检查关于u的所有查询,若存在查询(u,v),且vis[v]=1,则利用并查集查找v祖宗,该祖宗即为lca
原理:u的祖宗为u向上查找的第一个邻接点没有访问完的节点,而并查集的父子节点关系是在访问完所有邻接节点后更新的,因此这个未更新完的节点当前就是这个集合的祖宗,因而可以通过并查集从v查询到lca
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void dfs(int x,int root)
{
vis[x]=root;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(vis[y]) continue;
dis[y]=dis[x]+edge[i];
dfs(y,root);
fa[y]=x;
}
for(int i=0;i<q[x].size();i++)
{
int y=q[x][i];
int id=qid[x][i];
if(vis[y]==vis[x])
{
int lca=find(y);
ans[id]=dis[x]+dis[y]-2*dis[lca];
}
else if(vis[y]!=0) ans[id]=-1;
}
}
时间复杂度: