ICA树(最近公共祖先,tarjan+倍增两种解法)

tarjan求lca
Tarjan 是一种 DFS 的思想。我们需要从根结点去遍历这棵树。

当遍历到某一个结点(称之为 x) 时,你有以下几点需要做的。

1将当前结点标记为已经访问。

2递归遍历所有它的子节点(称之为 y),并在递归执行完后用并查集合并 x 和 y。

3遍历与当前节点有查询关系的结点(称之为 z)(即是需要查询 LCA 的另一些结点),如果 z 已经访问,那么 x 与 z 的 LCA 就是 getfa(z)(这个是并查集中的查找函数),输出或者记录下来就可以了。

这是伪代码

void tarjan(int x){
    //在本代码段中,s[i]为第i个子节点 , t[i]为第i个和当前节点有查询关系的结点。
    vis[x]=1;//标记已经访问,vis是记录是否已访问的数组
    for (i=1;i<=子节点数;i++){//枚举子节点 (递归并合并)
        tarjan(s[i]);
        marge(x,s[i]);//并查集合并
    }
    for (i=1;i<=有查询关系的结点数;i++){
        if (vis[t[i]]){
            cout<<x<<"和"<<t[i]<<"的LCA是"<<getfa(t[i])<<endl;//如果t[i]已经访问了输出(getfa是并查集查找函数)
        }
    }
}

倍增求lca
用邻接表存储一棵树, 并用from[]数组记录各结点的父节点, 其中没有父节点的就是root.

parents[u][]数组存储的是u结点的祖先结点.
如parents[u][0], 是u结点的2⁰祖先结点, 即1祖先, 也即父节点. 在输入过程中可以直接得到.
parents[u][1], 是u结点的2¹祖先结点,即2祖先, 也即父亲的父亲
parents[u][2], 是u结点的2²祖先结点, 即4祖先, 也即(父亲的父亲)的(父亲的父亲), 也就是爷爷的爷爷.

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

const int maxn = 10005;
int parents[maxn][20], depth[maxn];
int n, from[maxn], root = -1;
vector<int> G[maxn];

void init()
{
    memset(parents, -1, sizeof(parents));
    memset(from, -1, sizeof(from));
    memset(depth, -1, sizeof(depth));
}

void getData()//获取图的邻接表存储,存储第一个父节点
{
    cin >> n;
    int u, v;
    for (int i = 1; i < n; ++i) {
        cin >> u >> v;
        G[u].push_back(v);
        parents[v][0] = u;
        from[v] = 1;
    }
    for (int i = 1; i <= n; ++i) {
        if (from[i] == -1) root = i;//找到根
    }
}

void getDepth_dfs(int u)//dfs求深度
{
    int len = G[u].size();
    for (int i = 0; i < len; ++i) {
        int v = G[u][i];
        depth[v] = depth[u] + 1;
        getDepth_dfs(v);
    }
}

void getDepth_bfs(int u)//bfs求深度
{
    queue<int> Q;
    Q.push(u);
    while (!Q.empty()) {
        int v = Q.front();
        Q.pop();
        for (int i = 0; i < G[v].size(); ++i) {
            depth[G[v][i]] = depth[v] + 1;
            Q.push(G[v][i]);
        }
    }
}

void getParents()//求祖先,核心
{
    for (int up = 1; (1 << up) <= n; ++up) {
        for (int i = 1; i <= n; ++i) {
            parents[i][up] = parents[parents[i][up - 1]][up - 1];
        }
    }
}

int Lca(int u, int v)
{
    if (depth[u] < depth[v]) swap(u, v);//让u的深度大
    int i = -1, j;
    while ((1 << (i + 1)) <= depth[u]) ++i;
    for (j = i; j >= 0; --j) {
        if (depth[u] - (1 << j) >= depth[v]) {
            u = parents[u][j];
        }
    }//调整uv到同一深度
    if (u == v) return u;
    //一起往上走
    for (j = i; j >= 0; --j) {
        if (parents[u][j] != parents[v][j]) {
            u = parents[u][j];
            v = parents[v][j];
        }
    }
    return parents[u][0];//注意返回的是父节点
}

void questions()
{
    int q, u, v;
    cin >> q;
    for (int i = 0; i < q; ++i) {
        cin >> u >> v;
        int ans = Lca(u, v);
        cout << ans << endl;
        //cout << u << " 和 " << v << " 的最近公共祖先(LCA)是: " << ans << endl; 
    }
}

int main()
{
    init();
    getData();
    depth[root] = 1;
    getDepth_dfs(root);
    //getDepth_bfs(root);
    getParents();
    questions();
}
/*
9
1 2
1 3
1 4
2 5
2 6
3 7
6 8
7 9
5
1 3
5 6
8 9
8 4
5 8
*/

洛谷模板题材传送门
洛谷传送门的两个解法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值