最近公共祖先--LCA问题

 最近公共祖先问题:给定一颗有根树,求其两个节点最近的公共祖先;节点的祖先即从节点至根的路径上的节点的集合。

朴素算法:从u的父亲开始顺着树往上枚举u的一个祖先并保存在一个列表L中,同样枚举v的祖先,当发现v的祖先第一次出现在u的祖先中,输出。

在线LCA算法:令L(u)为u的深度,设L(u)<L(v),若u是v的父亲,LCA(u,v)=u,否则:LCA(u,v)=LCA(u,father(v));

LCA的离线(Tarjan)算法:利用DFS+并查集,算法流程:对于新搜索到的一个结点,首先创建由这个结点构成的集合,再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前结点的所有子树搜索完。这时把当前结点也设为已被检查过的,同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。算法伪代码如下:

void LCA(u)    
{   MAKE-SET(u);//建议一个集合,只包含元素u,且u为元素的代表元         
    ancestor[Find-SET(u)]=u;         
    for(u的每一个孩子v)         
    {   LCA(v);             
        Union(u,v);             
        ancestor[Find-SET(u)]=u;           
    }           
    color[u]=black;       
    for(Q(u)中的所有元素v) // (u,v)是被询问的点对        
    {   if(color[v]==black)            
        {   ancestor[Find-SET(v)](u,v的最近公共祖先);                  
        }          
    }    
} 

POJ 1330题目链接:http://poj.org/problem?id=1330现在的问题是建立一个包含元素u的集合,可以使用邻接表存储。至于什么是邻接表,数据结构上有,建议用向量作邻接表:

向量做法:

#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
using namespace std;
const int MAX=10010;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,father[MAX],rank[MAX],ancestors[MAX],Indegree[MAX];
vector<int> Adj[MAX],temp[MAX];
bool visited[MAX];
void Init()
{   for(int i=0;i<MAX;i++)
    {   father[i]=i;
        Adj[i].clear();
        temp[i].clear();
    }
    fill(rank,rank+MAX,1);    
    CLR(visited,false);
}
int Find(int u)
{   return father[u]==u?u:Find(father[u]);
}
void Union(int u,int v)
{   u=Find(u);
    v=Find(v);
    if(u==v) return ;
    if(rank[u]>rank[v])
    {   rank[u]+=rank[v];
        father[v]=u;
    }
    else
    {   rank[v]+=rank[u];
        father[u]=v;
    }
}
void LCA(int u)
{   ancestors[Find(u)]=u;//设定自己为祖先
    for(vector<int>::size_type i=0;i<Adj[u].size();i++) 
    {   LCA(Adj[u][i]);//DFS~~~
        Union(u,Adj[u][i]);
        ancestors[Find(u)]=u;
    }
    visited[u]=true;
    for(vector<int>::size_type i=0;i<temp[u].size();i++)
    {   if(visited[temp[u][i]])
           cout<<ancestors[Find(temp[u][i])]<<endl; 
    }
}
int main()
{   int k,u,v;
    scanf("%d",&k);
    while(k--)
    {   scanf("%d",&n);
        Init();
        for(int i=0;i<n-1;i++)
        {   scanf("%d%d",&u,&v);
            Adj[u].push_back(v);
            Indegree[v]++;
        }
        scanf("%d%d",&u,&v);
        temp[u].push_back(v);
        temp[v].push_back(u);
        for(int i=1;i<=n;i++)
            if(!Indegree[i]) 
            {   LCA(i);//找到树根 
                break; 
            }     
    }
    return 0;
}

邻接表的用处比较的多,写一下关于邻接表的知识,邻接表是图的一种链式存储结构,它对于图中的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对有向图是以顶点vi为尾的弧)。

下面是用邻接表的做法:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX=10010;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,Indegree[MAX],ancestors[MAX];
bool visited[MAX];
class UnionFindSet {   
public:             
    void Union(int u,int v)       
    {   u=Find(u),v=Find(v);  
        if(u==v) return ;         
        if(rank[u]>rank[v])           
        {   rank[u]+=rank[v];               
            father[v]=u;           
        }           
        else          
        {   rank[v]+=rank[u];               
            father[u]=v;           
        }       
    }       
    int Find(int u)       
    {   return father[u]==-1?u:Find(father[u]);       
    }       
    void Init()       
    {   CLR(father,-1);           
        fill(rank,rank+MAX,1);       
    }   
private:       
    int father[MAX];       
    int rank[MAX];   
}U;  
struct ArcNode{
    void Link(int u,int v)     
    {   next[num]=prior[u];         
        data[num]=v;         
        prior[u]=num++;     
    }   
    void Init()
    { CLR(prior,-1);num=0;} 
    int prior[MAX],next[MAX],data[MAX],num; //prior[]该弧所指向的顶点的位置,即邻接点,next[]指向下一条弧或结点,data[]为顶点信息 
}Arc,Que;
void LCA(int u)
{   ancestors[U.Find(u)]=u;
    for(int i=Arc.prior[u];i!=-1;i=Arc.next[i])
    {   LCA(Arc.data[i]);
        U.Union(u,Arc.data[i]);
        ancestors[U.Find(u)]=u; 
    } 
    visited[u]=true;
    for(int i=Que.prior[u];i!=-1;i=Que.next[i])
    {   if(visited[Que.data[i]])
           cout<<ancestors[U.Find(Que.data[i])]<<endl; 
    }  
}      
int main()
{   int u,v,n,k;
    scanf("%d",&k);
    while(k--)
    {   U.Init();
        Arc.Init(); Que.Init(); 
        CLR(Indegree,0);
        CLR(visited,false);
        scanf("%d",&n);
        for(int i=0;i<n-1;i++)
        {   scanf("%d%d",&u,&v);
            Arc.Link(u,v);
            Indegree[v]++;
        }
        scanf("%d%d",&u,&v);
        Que.Link(u,v); Que.Link(v,u);
        for(int i=1;i<=n;i++)
            if(!Indegree[i]) {LCA(i); break; }
    }
    return 0;
}

邻接表模板,转载自:http://blog.acmol.com/

template<int Max,int MaxE> 
struct Graph {     
    void add(int u,int v,int len)     
    {    Next[top]=Head[u];         
         Len[top]=len; //用来保存权值        
         Num[top]=v;         
         Head[u]=top++;     
    }     
    void init()     
    {    CLR(Head,-1);top=0;     
    }     
    int Head[Max],Next[MaxE],Num[MaxE],top,Len[MaxE]; 
}; 

不过邻接表模板会有一些小变种:
一、在有些图中不需要存边的权值,这时可以把Len数组省去,并且add函数中不需要len这个参数了。
二、有时候需要用到某个点出发的边的条数。可以用一个Size数组记录一下,add时增加一下Size数组的值即可。

template<int Max,int MaxE> 
struct Graph2 {     
    void add(int u,int v)     
    {    Next[top]=Head[u];         
         Num[top]=v;         
         Head[u]=top++;         
         Size[u]++;     
    }     
    void init()     
    {    CLR(Head,-1);CLR(Size,0);top=0;     
    }     
    int Head[Max],Next[MaxE],Num[MaxE],Size[Max],top; 
}; 

三、在最大流中,需要每次加正向边的同时加一条容量为0的反向边也。

四、在最小费用最大流中,一般需要加一个容量为0,费用为正向边费用相反数的边。

涉及到LCA问题的题目:POJ 1470,1986,3237,3728,.3417,HDU 2586,1269,2874,3380等~



 


 

你可能感兴趣的:(数据结构,算法,struct,存储,Class,Graph)