首页 > 代码库 > POJ 1330 Nearest Common Ancestors LCA(在线RMQ,离线Tarjan)

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找到第二个点,记录路径,两条路径的第一个重合点也是公共祖先。