1. 程式人生 > >Tarjan演算法求LCA(最近公共祖先)

Tarjan演算法求LCA(最近公共祖先)

LCA的離線演算法。複雜度為O(n+q)。

這個演算法充分利用了dfs樹的結構。

對於每個節點u,關於它的詢問(u,v)只有兩種。(假設先dfs(u)後dfs(v))

1、v在u的子樹內。

此時LCA(u,v) = u.

2、v不在u的子樹內。

⑴假設v在u的父親的另一棵子樹內。

此時LCA(u,v) = father[u].

⑵如果不滿足條件⑴,則v可能在u的父親的父親的另一棵子樹內。

而此時LCA(u,v) = father[ father[u] ].

⑶……

觀察一下,是不是發現了什麼呢?

沒錯,不論是哪種情況,LCA(u,v)都與u和father[ ]有某種關係。我們能不能抓住這種關係呢?

我們繼續觀察,一直向上取father[ ],貌似和並查集的FIND操作很像呢。

我們用並查集的角度依次考慮上面的情況試試看。

1、v在u的子樹內。

此時dfs(u)還在棧中,沒有執行完,此時沒有向上取father[ ],說明此時u是根。

2、v不在u的子樹內。

⑴假設v在u的父親的另一棵子樹內。

此時的dfs(u)已經執行完並出棧。此時向上取了一次father[ ],說明此時u的父親是根。

⑵如果不滿足條件⑴,則v可能在u的父親的父親的另一棵子樹內。

同理,此時dfs(u的父親)也已經執行完並出棧。此時向上取了兩次father[ ],說明此時u的父親的父親是根。

⑶……

綜上,我們只要保證當dfs(u)在棧中的時候,u是根;當dfs(u)不在棧中的時候,father[u]是根就行了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define FileIn  freopen("in.ads","r",stdin)
#define FileOut freopen("out.ads","w",stdout)

#define N 155
#define M 22555

struct Vertex
{
    int head;
}V[N],Qv[N];

struct Edge
{
    int v,next;
}E[M],Qe[M];

int top,pre[N];

bool used[N];

void Init()
{
    top = 0;
    memset(V,-1,sizeof(V));
    memset(Qv,-1,sizeof(Qv));
    memset(pre,-1,sizeof(pre));
    memset(used,0,sizeof(used));
}

int Root(int x)
{
    if(pre[x] != -1)
        return pre[x] = Root(pre[x]);
    else
        return x;
}

void Union(int a,int b)
{
    int r1 = Root(a);
    int r2 = Root(b);
    if(r1 != r2)
        pre[r2] = r1;
}

void Add_Edge(int u,int v)
{
    E[top].v = v;
    E[top].next = V[u].head;
    V[u].head = top++;
}

void Add_Qedge(int u,int v)
{
    Qe[top].v = v;
    Qe[top].next = Qv[u].head;
    Qv[u].head = top++;
}

void Tarjan(int u)
{
    used[u] = true;
    for(int i=Qv[u].head;i!=-1;i=Qe[i].next)
    {
        int v = Qe[i].v;
        if(used[v])
            printf("The LCA of (%d,%d) is -> %d\n",u,v,Root(v));
    }
    for(int i=V[u].head;i!=-1;i=E[i].next)
    {
        int v= E[i].v;
        if(used[v])
            continue;
        Tarjan(v);
        Union(u,v);
    }
}

int main()
{
    FileIn;
    int n,m,u,v,Q;
    while(~scanf("%d%d",&n,&m))
    {
        Init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            Add_Edge(u,v);
            Add_Edge(v,u);
        }
        scanf("%d",&Q);
        while(Q--)
        {
            scanf("%d%d",&u,&v);
            Add_Qedge(u,v);
            Add_Qedge(v,u);
        }
        Tarjan(1);
    }
	return 0;
}