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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 你可以通过以下几个步骤来准备蓝桥杯C/C++比赛: 1. 学习C/C++语言基础知识,包括语法、数据类型、运算符、流程控制等。 2. 练习编写算法和数据结构,例如排序、查找、树、图等。 3. 参加在线编程练习,例如LeetCode、洛谷、牛客网等,提高编程能力和解题能力。 4. 阅读蓝桥杯历年真题,了解比赛的考察范围和难度。 5. 参加模拟赛和培训班,提高比赛经验和技巧。 希望以上建议能够帮助你准备蓝桥杯C/C++比赛。 ### 回答2: 准备蓝桥杯C/C++编程比赛的关键步骤如下: 1. 学习C/C++语言基础知识:了解C/C++的语法规则和常用库函数,掌握变量的定义和使用、条件语句、循环结构等编程基本概念。 2. 理解算法和数据结构:熟悉常用的算法和数据结构,包括数组、字符串、链表、栈、队列和树等,能够灵活运用它们解决实际问题。 3. 多做编程练习题:通过做一些编程练习题提高自己的编程能力,例如POJ、LeetCode等在线编程平台上的题目可以供选择。多做一些具有挑战性的题目,加深对算法和数据结构的理解,并培养解决问题的思维方式。 4. 查阅相关资料:阅读一些与蓝桥杯竞赛相关的书籍和网上的教程,了解考试的题型、要求和考察的重点,提前准备一些常见的题型并查找相关实例。 5. 组队参加训练:找一些具有一定编程实力的同学组成队伍,一起参加C/C++编程训练营或组织小规模的训练,相互讨论问题,共同进步。 6. 参加模拟赛:参加一些模拟赛进行考试模拟训练,熟悉考试流程、时间管理和答题技巧。 7. 时间规划和备考:根据自己的实际情况,合理安排备考时间,多进行编程实践,磨练自己的编程技能,并适当进行复习和总结。 8. 勇敢参赛:在比赛开始前保持积极心态,相信自己的实力,充分发挥已掌握的知识和技能,勇敢去参赛并努力取得好成绩。 通过以上准备措施,可以提高解题速度、优化代码逻辑,加强对算法和数据结构的理解,并增强在C/C++编程方面的技能,为蓝桥杯C/C++编程比赛做好充分准备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值