最近公共祖先(LCA):tarjan与倍增

首先说一下离线算法:tarjan
该算法基于dfs和并查集。

对于求 LCAuv ,当返回到节点 x 时,必然已经访问完了 x 的子树。此时将子树上的节点的祖先标记为 x 。这样递归处理下去,当 u v 第一次被标记时, x 就是它俩的最近公共祖先。

实现:
从根节点向下搜索时,搜索完一个节点时,将它放入并查集中,与其父亲节点连边。再判断该节点对应的要询问的节点是否已经被访问过。若已访问过,那么最近公共祖先就是另一个节点在并查集中的fa。

当然,对于多次询问如果一定要按顺序来求解的话,tarjan就无用武之地的。所以对于多组询问,要先保存询问,按照dfs顺来求解。这才是tarjan算法巧妙的地方。

附tarjan模板:

int getroot(int a)
{
    if(fa[a]==a)return a;
    return fa[a]=getroot(fa[a]);
}

void Union(int a,int b)
{
    fa[getroot(a)]=getroot(b);
}

void tarjan(int u)
{
    for(node *p=adj[u];p!=NULL;p=p->next)
    {
        tarjan(p->v);
        Union(p->v,u);
    }
    vis[u]=1;

    for(int i=1;i<=ask[u][0];++i)
        if(vis[ask[u][i]]==1)
            printf("%d\n",getroot(ask[u][i]));
}

void init()
{
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d%d",&a,&b);
        ask[a][++ask[a][0]]=b;
        ask[b][++ask[b][0]]=a;
    }
    for(int i=1;i<=n;++i)
        if(!in[i])
        {
            tarjan(i);
            break;
        }
}

而倍增则是在线算法。

通俗地说,就是两个节点同时往上走第一次相遇的地方。

实现:
先预处理出数组 f
f[i][j] 表示节点 i 的第 2j 个祖先。
得到递推式: f[i][j]=f[f[i][j1]][j1]
同时需要预处理出节点 i 的深度,这样在倍增时才能保证正确性。
若两个点深度不同,肯定是将深的那个点往上移动直到两者深度一致。

附模板:

void dfs(int u)
{
    int v ;
    for(node *p=adj[u];p!=NULL;p=p->next)
    {
        v=p->v;
        f[v][0]=u ;
        dep[v]=dep[u]+1 ;
        dfs(v);
    }
}

void init()
{
    dep[root]=0 ;
    dfs(root);
    f[root][0]=root ;
    for(int j=1;j<MAXK;++j)
        for(int i=1;i<=n;++i)
            f[i][j]=f[f[i][j-1]][j-1];
}

void adjust(int &u,int val)
{
    for(int j=MAXK-1;j>=0;--j)
        if(dep[f[u][j]]>=val)
            u=f[u][j];
}

int solve(int u,int v)
{
    if(dep[u]>dep[v])adjust(u,dep[v]);
    else if(dep[u]<dep[v])adjust(v,dep[u]);
    if(u==v)return u;
    for(int j=MAXK-1;j>=0;--j)
        if(f[u][j]!=f[v][j])
            u=f[u][j] ,v=f[v][j];
    return f[u][0];
}

习题:
poj1330(一道裸题)
noip2013货车运输(有难度)附题解 题解链接

你可能感兴趣的:(LCA,Tarjan,倍增)