POJ 1330 Nearest Common Ancestors LCA(在線RMQ,離線Tarjan)

鏈接:http://poj.org/problem?id=1330

題意:只看題目就知道題目是什麼意思了,最近公共祖先,求在一棵樹上兩個節點的最近公共祖先。

思路:求最近公共祖先有兩種算法,在線和離線,在線方法是用RMQ求LCA,一句話總結就是在從DFS時,從第一個點到第二個點的最短路徑中深度最淺的點就是公共祖先,用RMQ處理,一般問題的最優解決方式的複雜度是O(NlogN)的預處理+N*O(1)的查詢。離線方法是Tarjan算法,將所有詢問的兩個點都記錄下來,在DFS過程中不斷將每個點自身作爲祖先,然後將所有子樹遍歷結束後,祖先變爲它的父節點,在過程中如果遍歷到某個節點,同時關於它的詢問的另一點已遍歷,則此時另一點的祖先就是它們的公共祖先,雖然算法不是很好說明白,不過看代碼還是很顯而易見的。

代碼:

在線算法:RMQ求LCA

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define eps 1e-8
#define INF 0x7fffffff
#define maxn 10005
#define PI acos(-1.0)
#define seed 31//131,1313
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
int stTable [maxn*2][32];
int preLog2[maxn*2];
int depth=0;
int d[maxn*2];
bool vis[maxn];
int bn=0,b[maxn*2]; //深度序列
int f[maxn*2];          //對應深度序列中的結點編號
int p[maxn];             //結點在深度序列中的首位置
int dis[maxn];          //結點到根的距離
int head[maxn];
void st_prepare(int n,int *array)
{
    preLog2[1]=0;
    for(int i=2;i<=n;i++)
    {
        preLog2[i]=preLog2[i-1];
        if((1<<preLog2[i]+1)==i)
        preLog2[i]++;
    }
    for(int i=n-1;i>=0;i--)
    {
        stTable[i][0]=array[i];
        for(int j=1;(i+(1<<j)-1)<n;j++)
        {
            stTable[i][j]=min(stTable[i][j-1],stTable[i+(1<<j-1)][j-1]);
        }
    }
    return ;
}
int query_min(int l,int r)
{
    int len=r-l+1,k=preLog2[len];
    return min(stTable[l][k],stTable[r-(1<<k)+1][k]);
}
int point[maxn];    //記錄每個點對應的第一條邊的序號
struct Edge
{
    int v;//連接點
    int next;//下一條從此邊的出發點發出的邊
}edge[maxn*2];
int top;
int init()
{
    memset(vis,0,sizeof(vis));
    memset(point,-1,sizeof(point));
    memset(dis,0,sizeof(dis));
    top=0;
    bn=0;
    depth=0;
}
int add_edge(int u,int v)
{
    edge[top].v=v;
    edge[top].next=point[u];//上一條邊的編號
    point[u]=top++;//u點的第一條邊編號變成head
}
void dfs(int u,int fa)
{
    int tmp=++depth;
    b[++bn]=tmp; f[tmp]=u; p[u]=bn;
    for (int i=point[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        if (v==fa) continue;
        dis[v]=dis[u]+1;//edge[i].v
        dfs(v,u);
        b[++bn]=tmp;
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        int tot,root=0,aa,bb;
        scanf("%d",&tot);
        for(int i=1;i<=tot;i++)
            head[i]=i;
        for(int i=1;i<=tot-1;i++)
        {
            scanf("%d%d",&aa,&bb);
            add_edge(aa,bb);
            add_edge(bb,aa);
            head[bb]=aa;
        }
        for(int i=1;i<=tot;i++)
        {
            if(head[i]==i)
            {
                root=i;
                break;
            }
        }
        dfs(root,root);
        st_prepare(tot*2-1,b);
        scanf("%d%d",&aa,&bb);
        if(p[aa]<p[bb])
            cout<<f[query_min(p[aa],p[bb])]<<endl;
        else cout<<f[query_min(p[bb],p[aa])]<<endl;
    }
}
離線算法:Tarjan算法

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define eps 1e-8
#define INF 0x7fffffff
#define maxn 10005
#define PI acos(-1.0)
#define seed 31//131,1313
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
int pre[maxn],point[maxn],point2[maxn];
bool vis[maxn];
struct Edge
{
    int v;//連接點
    int next;//下一條從此邊的出發點發出的邊
} edge[maxn*2];
struct Query
{
    int v;
    int w;
    int next;
} query[maxn];
int top,top2;
int init()
{
    memset(vis,0,sizeof(vis));
    memset(point,-1,sizeof(point));
    memset(point2,-1,sizeof(point2));
    top=0;
    top2=0;
}
int add_edge(int u,int v)
{
    edge[top].v=v;
    edge[top].next=point[u];//上一條邊的編號
    point[u]=top++;//u點的第一條邊編號變成head
}
int findset(int x) //並查集
{
    if(x!=pre[x])
    {
        pre[x]=findset(pre[x]); //路徑壓縮
    }
    return pre[x];
}
int add_query(int u,int v)
{
    query[top2].v=v;
    query[top2].w=-1;
    query[top2].next=point2[u];//上一條邊的編號
    point2[u]=top2++;//u點的第一條邊編號變成head
    query[top2].v=u;
    query[top2].w=-1;
    query[top2].next=point2[v];//上一條邊的編號
    point2[v]=top2++;//u點的第一條邊編號變成head
}
int lca(int u,int f) //當前節點,父節點
{
    pre[u]=u;  //設立當前節點的集合
    for(int i=point[u]; i!=-1; i=edge[i].next)
    {
        if(edge[i].v==f)
            continue;
        lca(edge[i].v,u);  //搜索子樹
        pre[edge[i].v]=u; //合併子樹
    }
    vis[u]=1; //以u點爲集合的點搜索完畢
    for(int i=point2[u]; i!=-1; i=query[i].next)
    {
        if(vis[query[i].v]==1)
            query[i].w=findset(query[i].v);
    }
    return 0;
}
int main()
{
    int root[maxn];
    int T;
    scanf("%d",&T);
    for(int ii=0; ii<T; ii++)
    {
        init();
        int tot,r=-1,a,b;
        scanf("%d",&tot);
        for(int i=1; i<=tot; i++)
            root[i]=i;
        for(int i=0; i<tot-1; i++)
        {
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
            root[b]=a;
        }
        for(int i=1; i<=tot; i++)
            if(root[i]==i)
                r=i;//樹的根
        scanf("%d%d",&a,&b);
        add_query(a,b);
        lca(r,r);
        //cout<<top2<<endl;
        for(int i=0;i<top2;i++)
        {
            if(query[i].w!=-1)
            printf("%d\n",query[i].w);
        }

    }
    return 0;
}

P.S.其實這道題只有一次詢問,所以先DFS第一個點,記錄路徑,然後DFS找到第二個點,記錄路徑,兩條路徑的第一個重合點也是公共祖先。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章