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<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
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<int> edge[MAXN];
    vector<int> 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)

根据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<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
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<int> edge[MAXN];

    void init(int n)
    {
        for(int i = 0; i < PP; i++)
            pow2[i] = (1<<i);

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

    }

    void addedge(int u ,int v)
    {
        edge[u].push_back(v);
    }

    void dfs(int u ,int d) //u是节点, d是深度
    {
        tim++;  //时间戳+1
        vis[u] = true;
        nodeId[tim] = u;   //记录与时间戳相对应的节点
        first[u] = tim; //记录下节点u首次访问的时间戳
        dep[tim] = d;  //根节点的深度为1
        int sz = edge[u].size();
        for(int i = 0; i < sz; i++)
        {
            int v = edge[u][i];
            if(vis[v] == false)
            {
                dfs(v, d + 1);
                tim++;
                nodeId[tim] = u;  //只要访问的不是叶子节点,tim都要重复增加
                dep[tim] = d;
            }
        }
    }

    void ST(int len)
    {
        int k = (int)(log(len+1.0) / log(2.0));

        for(int i = 1; i <= len; i++)
            dp[i][0] = i;

        for(int j = 1; j <= k; j++)
            for(int i = 1; i + pow2[j] - 1 <= len; i++)
            {
                int a = dp[i][j-1]; //a, b均为下标
                int b = dp[i+pow2[j-1]][j-1];
                if(dep[a] < dep[b]) dp[i][j] = a;
                else dp[i][j] = b;
            }
    }

    int RMQ(int x ,int y)
    {
        int k = (int)(log(y-x+1.0) / log(2.0));
        int a = dp[x][k];
        int b = dp[y-pow2[k]+1][k];
        if(dep[a] < dep[b]) return a;
        else return b;
    }

    int LCA(int u ,int v)
    {
        int x = first[u];
        int y = first[v];
        if(x > 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值