算法笔记——最近公共祖先(LCA)

利用LCA求解树上任意两点距离

u和v的之间的距离为u到树根上的距离加上v到树根上的距离减去2×lca到树根的距离

dist[u]+dist[v]-2\times dist[lca]

一、暴力搜索法

 

时间复杂度均为O(n)

二、树上倍增法

在树上构造ST表

F[i][j]表示i节点向上走2^{j}步到达的节点。

其中F[i][0]i父节点

递推公式 F[i][j]=F[F[i][j-1],j-1] ,i=1,2,……n,  j=0,1,2,……k,  k=log_{2}n

即走2^{j}步分为两步2^{j-1}

采用同步前进法的思想,但利用倍增的走法。

1、使y到与x同深度的节点

2、x,y同时向上跳

k从大到小遍历,若f[x][i]和f[y][i]相同,说明跳的过高,高于lca,不进行任何操作;

如果不相同,则将x,y上跳至该位置。直到k=0结束后,当前x,y所指节点的父节点即是其lca

时间复杂度:构造ST表需要O(nlogn),单词查询需要O(logn)

模板代码

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表需要O(nlogn),单次查询需要O(1)

四、离线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;
    }
}

时间复杂度:O(n+m)

### Targan Algorithm for Lowest Common Ancestor (LCA) The Tarjan offline algorithm is one of the most efficient methods to solve the LCA problem, especially when dealing with multiple queries on static trees. It leverages Depth First Search (DFS) and Union-Find data structures to compute LCAs efficiently. #### Explanation Tarjan's algorithm works by performing a DFS traversal of the tree while maintaining disjoint sets using the Union-Find structure. During this process, it processes all query pairs involving nodes encountered during the traversal. Once a node finishes its DFS exploration, it marks itself as processed and resolves any pending queries where it serves as an ancestor[^1]. Here’s how the key components work: - **Union-Find Data Structure**: This helps manage equivalence classes among nodes dynamically. - **DFS Traversal**: As we traverse through the tree via DFS, every time a new vertex `v` is visited, union operations are performed between `v` and its parent until reaching the root or another already explored part of the graph. Once the DFS completes at some point within the subtree rooted at `v`, if there exist unresolved queries concerning `v`, these will now have their answers determined because either side must belong under the same connected component due to previous unions made along paths leading upwards towards shared roots/ancestors. Below is Python implementation demonstrating usage of such approach based upon principles outlined above regarding solving LCA problems utilizing Tarjan Offline Methodology combined appropriately structured code logic steps accordingly explained inline comments throughout provided snippet below : ```python class UnionFind: def __init__(self, n): self.parent = list(range(n)) def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def unite(self, x, y): fx, fy = self.find(x), self.find(y) if fx != fy: self.parent[fy] = fx def tarjan_offline_lca(root, adj_list, queries): n = len(adj_list) uf = UnionFind(n) ancestors = [-1]*n result = {} query_map = {i: [] for i in range(n)} for u, v in queries: query_map[u].append(v) def dfs(node): stack = [node] nonlocal ancestors while stack: current_node = stack[-1] if ancestors[current_node]==-1: ancestors[current_node]=current_node unprocessed_children=False for child in adj_list[current_node]: if ancestors[child]==-1: unprocessed_children=True stack.append(child) if not unprocessed_children: popped=stack.pop() for other_node in query_map[popped]: lca_result=(uf.find(other_node)==uf.find(popped))and(ancestors[popped])or(-1) result[(min(popped,other_node),max(popped,other_node))] =lca_result if popped!=root: uf.unite(popped , ancestors[popped]) if stack: ancestors[stack[-1]]=popped dfs(root) return result ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值