zoukankan      html  css  js  c++  java
  • Algorithm,LCA,Tarjan,深搜+并查集,最近公共祖先

    思想:

    要查找 lca(u,v)

    dfs到两个结点



    入栈动作:

    建立自己的集合,代表元(祖先)为自己

    出栈动作:

    合并自己集合到父结点集合(祖先为父结点)


    注意到一个结点必先入栈,接着另一个结点入栈,设u先发现,v后发现

    则发现v后,u为黑结点(已经出栈)或灰结点(已经入栈,但未出栈)

    发现v时,栈中所有灰结点为v的祖先路径

    若此时u为灰,则lca(u,v)为u,等于u的代表元(因为u未出栈,集合没有向父结点合并)

    若此时u为黑,则此时u所在集合的代表元就是lca(u,v),且是此时栈中一个灰结点


    这个文章图文并茂一看就懂:

    http://scturtle.is-programmer.com/posts/30055

    tarjan算法的步骤是(当dfs到节点u时):
    1 在并查集中建立仅有u的集合,设置该集合的祖先为u
    1 对u的每个孩子v:
       1.1 tarjan之
       1.2 合并v到父节点u的集合,确保集合的祖先是u
    2 设置u为已遍历
    3 处理关于u的查询,若查询(u,v)中的v已遍历过,则LCA(u,v)=v所在的集合的祖先
     
    举例说明(非证明):


    假设遍历完10的孩子,要处理关于10的请求了
    取根节点到当前正在遍历的节点的路径为关键路径,即1-3-8-10
    集合的祖先便是关键路径上距离集合最近的点
    比如此时:
        1,2,5,6为一个集合,祖先为1,集合中点和10的LCA为1
        3,7为一个集合,祖先为3,集合中点和10的LCA为3
        8,9,11为一个集合,祖先为8,集合中点和10的LCA为8
        10,12为一个集合,祖先为10,集合中点和10的LCA为10
    你看,集合的祖先便是LCA吧,所以第3步是正确的
    道理很简单,LCA(u,v)便是根至u的路径上到节点v最近的点

    为什么要用祖先而且每次合并集合后都要确保集合的祖先正确呢?
    因为集合是用并查集实现的,为了提高速度,当然要平衡加路径压缩了,所以合并后谁是根就不确定了,所以要始终保持集合的根的祖先是正确的
    关于查询和遍历孩子的顺序:
    wikipedia上就是上文中的顺序,很多人的代码也是这个顺序
    但是网上的很多讲解却是查询在前,遍历孩子在后,对比上文,会不会漏掉u和u的子孙之间的查询呢?
    不会的
    如果在刚dfs到u的时候就设置u为visited的话,本该回溯到u时解决的那些查询,在遍历孩子时就会解决掉了
    这个顺序问题就是导致我头大看了很久这个算法的原因,也是絮絮叨叨写了本文的原因,希望没有理解错= =

    最后,为了符合本blog风格,还是贴代码吧:

    int f[maxn],fs[maxn];//并查集父节点 父节点个数
    bool vit[maxn];
    int anc[maxn];//祖先
    vector<int> son[maxn];//保存树
    vector<int> qes[maxn];//保存查询
    typedef vector<int>::iterator IT;
     
    int Find(int x)
    {
        if(f[x]==x) return x;
        else return f[x]=Find(f[x]);
    }
    void Union(int x,int y)
    {
        x=Find(x);y=Find(y);
        if(x==y) return;
        if(fs[x]<=fs[y]) f[x]=y,fs[y]+=fs[x];
        else f[y]=x,fs[x]+=fs[y];
    }
     
    void lca(int u)
    {
        anc[u]=u;
        for(IT v=son[u].begin();v!=son[u].end();++v)
        {
            lca(*v);
            Union(u,*v);
            anc[Find(u)]=u;
        }
        vit[u]=true;
        for(IT v=qes[u].begin();v!=qes[u].end();++v)
        {
            if(vit[*v])
                printf("LCA(%d,%d):%d
    ",u,*v,anc[Find(*v)]);
        }
    }

    ref:
    http://purety.jp/akisame/oi/TJU/
    http://en.wikipedia.org/wiki/Tarjan%27s_off-line_least_common_ancestors_algorithm
    http://techfield.us/blog/2008/11/lowest_common_ancester_tarjan_alogrithm/

     

  • 相关阅读:
    网页加速的14条优化法则 网站开发与优化
    .NET在后置代码中输入JS提示语句(背景不会变白)
    C语言变量声明内存分配
    SQL Server Hosting Toolkit
    An established connection was aborted by the software in your host machine
    C语言程序设计 2009春季考试时间和地点
    C语言程序设计 函数递归调用示例
    让.Net 程序脱离.net framework框架运行
    C语言程序设计 答疑安排(2009春季 110周) 有变动
    软件测试技术,软件项目管理 实验时间安排 2009春季
  • 原文地址:https://www.cnblogs.com/threef/p/3209600.html
Copyright © 2011-2022 走看看