P3379 【模板】最近公共祖先(LCA)

题解

lca入门:题解 P3379 【【模板】最近公共祖先(LCA)】

那些年我用lca干过的事:

  • 建虚树

在这里插入图片描述


代码

带常数优化的lca - 1

int Log[N];//log2(x)
//int dfn[N], cnt = 0;//各节点的编号
int fa[N][20];
int dep[N];//树的深度

//常数初始化
void initLog(){
    Log[0] = -1, Log[1] = 0;
    for (int i = 2; i <= N; ++i) {
        Log[i] = Log[i / 2] + 1;
    }
}

void dfs(int u, int f) {
    //dfn[u] = ++cnt;
    dep[u] = dep[f] + 1;
    fa[u][0] = f;

    for (int i = 1; (1 << i) <= n; ++i) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }

    for (int i = 0; i < e[u].size(); ++i) {
        int v = e[u][i];
        if (v != f)
            dfs(v, u);
    }
}

int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    while (dep[u] > dep[v])
        u = fa[u][Log[dep[u] - dep[v]]];
    if (u == v) return v;
    for (int i = Log[dep[u]]; i >= 0; --i) {
        if (fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    return fa[u][0];
}
/*
使用顺序:
	initLog();
	dfs(root,0);
	int p=lca(u,v);
*/

带常数优化的lca - 2

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;

int n, m;
int r;//root
int d[N];//分层 depth
int f[N][40];//倍增跳跃 f[u][i]表示u向上跳2^i格的祖先

struct edge {
    int next, to;
} e[N * 2];
int head[N], tot = 0;

void add(int x, int y) {
    e[++tot] = {head[x], y};
    head[x] = tot;
}

int lg[N];

void dfs(int u, int fa) {
    d[u] = d[fa] + 1;//记录深度
    f[u][0] = fa;//记录父亲
    for (int i = 1; i <= lg[d[u]]; ++i) {
        f[u][i] = f[f[u][i - 1]][i - 1];
    }//直接处理从u往上跳的所有祖先

    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (v != fa) dfs(v, u);
    }
}

int lca(int x, int y) {
    if (d[x] < d[y])swap(x, y);//假设x比y深
    while (d[x] > d[y])
        x = f[x][lg[d[x] - d[y]] - 1];//逐渐跳到与y同层

    if (x == y) return x;//重叠 说明y是x的祖先
    for (int i = lg[d[x]] - 1; i >= 0; --i) {//不重合 x和y一起往上跳
        if (f[x][i] != f[y][i]) {
            //只要父亲不同就跳 父亲相同就停止
            x = f[x][i];
            y = f[y][i];
            //最后一次是没有跳上去的
        }
    }
    return f[x][0];//输出最后一次往上跳
}

int main() {
    scanf("%d%d%d", &n, &m, &r);
    int x, y;
    for (int i = 1; i < n; ++i) {
        scanf("%d%d", &x, &y);
        add(x, y);
        add(y, x);
    }
    //5e5 最多2^20
    for (int i = 1; i <= n; ++i) {//预先算出log2(i)+1的值 常数优化
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }

    dfs(r, 0);//分层+倍增

    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        printf("%d\n", lca(x, y));
    }
    return 0;
}

不开O2过不了,也可能是头文件的原因?

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;

int n, m, k;
int r;//root
int d[N];//分层
int f[N][40];//倍增跳跃 f[u][i]表示u向上跳i格的祖先

struct edge {
    int next, to;
} e[N * 2];
int head[N], tot = 0;

void add(int x, int y) {
    e[++tot] = {head[x], y};
    head[x] = tot;
}

void dfs(int u) {
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if (!d[v]) {//v从未被访问过
            d[v] = d[u] + 1;//v是u的儿子
            f[v][0] = u;//v向上跳一格一定是x
            dfs(v);
        }
    }
}

int lca(int x, int y) {
    if (d[x] < d[y])swap(x, y);//假设x比y深
    for (int i = 20; i >= 0; --i) {
        if (d[f[x][i]] >= d[y]) {
            /*
                x往上大跳 跳过头就换小跳
                如果仍然比y深 就跳到f[x][i]的位置上
                逐渐跳到与y同层
            */
            x = f[x][i];
        }
    }
    if (x == y) return x;//重叠 说明y是x的祖先
    for (int i = 20; i >= 0; --i) {//不重合 x和y一起往上跳
        if (f[x][i] != f[y][i]) {
            //只要父亲不同就跳 父亲相同就停止
            x = f[x][i];
            y = f[y][i];
            //最后一次是没有跳上去的
        }
    }
    return f[x][0];//输出最后一次往上跳
}

int main() {
    scanf("%d%d%d", &n, &m, &r);
    int x, y;
    for (int i = 1; i < n; ++i) {
        scanf("%d%d", &x, &y);
        add(x, y);
        add(y, x);
    }

    d[r] = 1;
    f[r][0] = 0;//以r为根
    dfs(r);//分层+倍增

    //核心代码
    for (int i = 1; i <= 20; ++i) {//倍增数组建立
        //层层蔓延
        for (int u = 1; u <= n; ++u) {
            //整理所有的点之间的关系
            f[u][i] = f[f[u][i - 1]][i - 1];
            //表示第u个点向上跳2^i个点 一定是其向上跳2^(i-1)个点向上跳2^(i-1)所到达的地方
            //倍增嘛 2^i=2^(i-1)+2^(i-1)
        }
    }

    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        printf("%d\n", lca(x, y));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值