POJ 1330 Nearest Common Ancestors 最近公共祖先模板/在线/离线

题意:求树上两个节点的最近公共祖先


算法一:tarjan


LCA(u) {
  Make-Set(u)
  ancestor[Find-Set(u)]=u  //设置u所在集合的祖先
  对于u的每一个孩子v {
   LCA(v)
   Union(v,u)              //把v生成的子集并入u中
   ancestor[Find-Set(u)]=u //防止采用树形启发式合并使u的集合根(代表)变化
  }
  checked[u]=true
  对于每个(u,v)属于P {
   if checked[v]=true
   then 回答u和v的最近公共祖先为 ancestor[Find-Set(v)]
  }
}

#include
#include
#include
#include
#include
#include
using namespace std;

/*
时间复杂度O(N+Q)
离线算法,必须先记录询问
对于每一对询问lac(u,v),在u,v的查询队列里各加一次
*/


const int MAXN = 10010;

class LCA_Tarjan
{
public:
    int n, father[MAXN];
    bool vis[MAXN];
    vector edge[MAXN];
    vector query[MAXN];

    void init(int n)
    {
        for(int i = 1; i <= n; i++)
        {
            vis[i] = false;
            edge[i].clear();
            query[i].clear();
        }
        make_set(n);
    }

    void make_set(int n) //下标从1开始
    {
        for(int i = 1; i <= n; i++)
            father[i] = i;
    }

    int find(int u)
    {
        if(u == father[u])
            return u;
        return father[u] = find(father[u]);
    }

    void Union(int u, int v) //可以优化
    {
        int fu = find(u);
        int fv = find(v);
        if(fv != fu)
            father[fv] = fu;
    }

    void tarjan(int u)
    {
        father[u] = u;
        int sz = edge[u].size();
        for(int i = 0; i < sz; i++)
        {
            tarjan(edge[u][i]);
            Union(u, edge[u][i]);
            father[find(u)] = u;
        }

        vis[u] = true;
        sz = query[u].size();
        for(int i = 0; i < sz; i++)
        {
            if(vis[query[u][i]] == true)
                cout << father[find(query[u][i])] << endl;
        }
    }
};

int main()
{
    LCA_Tarjan t;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d", &t.n);
        t.init(t.n);

        int u, v;
        bool notRoot[MAXN] = {0};
        for(int i = 1; i < t.n; i++)
        {
            scanf("%d %d", &u, &v);
            t.edge[u].push_back(v);
            notRoot[v] = true;
        }

        scanf("%d %d", &u, &v);
        t.query[u].push_back(v);
        t.query[v].push_back(u);
        for(int i = 1; i <= t.n; i++)
            if(notRoot[i] == false) {t.tarjan(i); break;}
    }
    return 0;
}



算法二LCARMQ的转化:


对有根树T进行DFS,将遍历到的结点按照顺序记下,我们将得到一个长度为2N – 1的序列,称之为T的欧拉序列F
每个结点都在欧拉序列中出现,我们记录结点u在欧拉序列中第一次出现的位置为pos(u)
POJ 1330 Nearest Common Ancestors 最近公共祖先模板/在线/离线_第1张图片

根据DFS的性质,对于两结点u、v,从pos(u)遍历到pos(v)的过程中经过LCA(u, v)有且仅有一次,且深度是深度序列B[pos(u)…pos(v)]中最小的
即LCA(T, u, v) = RMQ(B, pos(u), pos(v)),并且问题规模仍然是O(N)的
这就证明了LCA问题可以转化成RMQ问题
LCA与RMQ问题可以互相转化,并且可以在O(N)的时间内完成!

/*
1.时间复杂度O(N*logN+Q)
2.在线算法
3.搜索之后n个节点得到2*n-1个编号
*/

#include
#include
#include
#include
#include
#include
using namespace std;

const int MAXN = 10010;
const int PP = 25;

class LCA_RMQ
{
public:
    int n;
    int pow2[PP]; //2^k
    int tim; //时间戳,从1开始
    int first[MAXN]; //节点第一次访问的时间戳
    int nodeId[MAXN*2]; //与相应时间戳对应的节点编号
    int dep[MAXN*2]; //深度
    int dp[MAXN*2][PP]; //搜索之后得到2*n-1个编号
    bool vis[MAXN]; //记录节点是否被访问过
    vector edge[MAXN];

    void init(int n)
    {
        for(int i = 0; i < PP; i++)
            pow2[i] = (1< y) swap(x,y);
        int index = RMQ(x,y);
        return nodeId[index];
    }
};

LCA_RMQ t;

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int u, v, rt;
        bool notRoot[MAXN] = {0};
        scanf("%d",&t.n);
        t.init(t.n);
        for(int i = 1; i < t.n; i++)
        {
            scanf("%d %d",&u,&v);
            t.addedge(u, v);
            notRoot[v] = true;
        }
        scanf("%d %d", &u, &v);

        for(int i = 1; i <= t.n; i++)
            if(notRoot[i] == false) {rt = i; break;}

        t.tim = 0;
        t.dfs(rt, 1); //根节点深度为1
        t.ST(2 * t.n - 1);
        int ans = t.LCA(u, v);
        printf("%d\n", ans);
    }
    return 0;
}


你可能感兴趣的:(LCA,POJ)