首页 > 其他分享 >【图论】最近公共祖先(LCA)

【图论】最近公共祖先(LCA)

时间:2023-08-12 15:12:48浏览次数:38  
标签:dep 图论 idx 祖先 int LCA 节点 dis

什么是LCA

最近公共祖先是相对于两个节点来说的,顾名思义,最近公共祖先即为两个节点公共的最近的祖先。

image

如上图,\(86\) 和 \(67\) 的 \(LCA\) 是 \(75\),\(67\) 和 \(27\) 的 \(LCA\) 是 \(50\)。

怎么求LCA

倍增法

我们先想想暴力怎么做:从这两个节点开始,一步一步往上跳,直到跳到了一起,那么就是 \(LCA\) 了。

那我们该怎么优化暴力呢?我们可以一次跳多个节点呀!如果我们在每一个节点上,都先预处理出 \(log(n)\) 个节点信息,分别表示向上走 \(1\) 步,\(2\) 步,\(4\) 步,\(8\) 步,\(\dots\),\(2^k\) 步所到达的节点。这样我们只需要 \(log(n)\) 步就可以走到根节点。

然后仍然套用之前暴力的方法向上走即可。

image

上面树的节点信息如下:

节点 节点信息
\(19\) 跳 \(1\) 步:\(15\);跳 \(2\) 步:\(9\);跳 \(4\) 步:\(1\)
\(20\) 跳 \(1\) 步:\(16\);跳 \(2\) 步:\(10\);跳 \(4\) 步:\(1\)
\(16\) 跳 \(1\) 步:\(10\);跳 \(2\) 步:\(4\)
\(10\) 跳 \(1\) 步:\(4\);跳 \(2\) 步:\(1\)

如何记录这些节点信息呢?

初始我们只知道向上走 \(1\) 步的信息。

然后根据走 \(1\) 步的信息,推出走 \(2\) 的信息。

再根据走 \(2\) 步的信息,推出走 \(4\) 步的信息。

\(\dots\)

很明显我们通过递推就可以求出来了。

我们用数组 \(f[u][k]\) 表示节点 \(u\) 向上走 \(2^k\) 步所走到的点,显然有 \(f[u][0] = u\) 的父亲节点,则我们的递推式则为:

\(f[u][k] = f[f[u][k - 1]][k - 1]\)

for (int k = 1; k <= 20; ++k)
    for (int u = 1; u <= n; ++u)
        f[u][k] = f[f[u][k - 1]][k - 1];

在求 \(x,y\) 两个节点的 \(LCA\) 时,如果 \(x,y\) 深度不同,则先让深度较大的节点向上跳到另一个节点所在的深度。记 \(dep[u]\) 表示节点 \(u\) 的深度,假设 \(dep[x] > dep[y]\),那么先让 \(x\) 倍增的向上跳 \(dep[x] - dep[y]\) 步。

int tmp = dep[x] - dep[y];
for (int i = 20; i >= 0; --i)
    if (tmp & (1 << i))
        x = f[x][i];

当两个节点深度相同时,就同时倍增的向上跳,但是又不能让他们跳过 \(LCA\)。具体来说,我们从大到小枚举 \(k\),判断 \(x, y\) 同时向上走 \(2 ^ j\) 步是否会相遇,如果不会,则向上跳 \(2 ^ j\) 步。重复这个过程,此时 \(x, y\) 就会跳到 \(LCA\) 的子儿子处,此时再进一步就是 \(LCA\)。

for (int k = 20; k >= 0; --k)
    if (f[x][k] != f[y][k])
        x = f[x][k], y = f[y][k];
if (x != y) x = f[x][0], y = f[y][0];
// 此时x,y就是lca

题目:

image

举个例子:

image

如果我们记录了每个点到根节点的边权和 \(dis_i\),则从 \(x\) 到 \(y\) 的路径边权和即为

\(dis_i + dis_j - dis_{LCA(i, j)}\)

代码:

#include <bits/stdc++.h>
#define N 100010
using namespace std;
int n, m;
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;
bool vis[N];
int dis[N], dep[N];
int f[N][30], fa[N];
void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; }
void dfs(int u, int father) {
	fa[u] = father;
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (j != father) {
        	dep[j] = dep[u] + 1;
        	dis[j] = dis[u] + w[i];
        	dfs(j, u);
        }
    }
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    int tmp = dep[x] - dep[y];
    for (int k = 20; k >= 0; --k)
        if (tmp & (1 << k))
            x = f[x][k];
    for (int k = 20; k >= 0; --k)
        if (f[x][k] != f[y][k])
            x = f[x][k], y = f[y][k];
    if (x != y) x = f[x][0], y = f[y][0];
    return x;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 1; i <= n - 1; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    dfs(1, 0);
    for (int i = 1; i <= n; ++i) f[i][0] = fa[i];
    for (int k = 1; k <= 20; ++k)
        for (int u = 1; u <= n; ++u)
            f[u][k] = f[f[u][k - 1]][k - 1];
    while (m--) {
        int x, y;
        cin >> x >> y;
        cout << dis[x] + dis[y] - 2 * dis[lca(x, y)] << '\n';
    }
    return 0;
}

tarjan求LCA

马上更新QAQ

标签:dep,图论,idx,祖先,int,LCA,节点,dis
From: https://www.cnblogs.com/popfront/p/17624729.html

相关文章

  • 图论2023年版
    图基本概念什么是图?图实际上是一个二元组\(G=(V,E)\),其中V是图中所有的点形成的点集,而E是边集每一条边都可以描述成(u,v),u和v都是V中的元素(v,w∈V)点数,即V中元素个数也称为G的阶图的分类图可以按照边有无方向分类,分为有向图和无向图无向图:每条边都是无向边的图称......
  • 图论一
    图论(1)图的存储直接存边inte[N];e[1]=3;//1->3有条边邻接表inth[N],e[N],ne[N],idx;voidadd(inta,intb){e[idx]=b,ne[idx]=h[a],h[a]=idx,idx++;}voidinit(){meset(h,-1,sizeof(h));//所有头结点为空}也可以用vectore[N];图的遍历树和图的深度......
  • Codeforces 1857D:Strong Vertices 与图论无关的出度最大统计
    1857D.StrongVerticesDescription:给定两个长度均为\(n\)的数组\(a\)和\(b\)(编号\(1\)~\(n\)),如果\(a_u-a_v\geqb_u-b_v\)\((u\neqv)\),那么从\(u\)到\(v\)建立一条有向边。"Strong"定义为:一个点\(V\)可以经过有向图中合法的通路到达其他所有的点。请求解出"......
  • 图论学习笔记
    图图论绘图在线图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系!点一般用字母v表示,如v1,v2,v3,v4一些简单的术语:路径:一些边组成的序列,满足第一条边的终点......
  • 关于 LCA 的简单记录
    最近做了几道LCA的题目。所以总结一下。首先我们来回顾一下倍增求LCA的流程吧。首先是初始化:进行bfs。处理出每层的深度。处理每个节点的\(2^k\)级父亲,方式为一个递推,即为由\(2^{k-1}\)级祖先的\(2^{k-1}\)祖先推出\(2^k\)级祖先。然后是每次的查询:把y......
  • 图论强联通分量(tarjan)算法
    图论强联通分量(tarjan)算法#include<bits/stdc++.h>usingnamespacestd;intn,m,cnt,cntb,ans;vector<int>edge[101];vector<int>belong[101];boolinstack[101];intdfn[101];intlow[101];stack<int>s;voidTarjan(intu){ ++cnt; dfn[u]=......
  • 【笔记】图论:网络流和二分图
    网络流的求法https://www.cnblogs.com/caijianhong/p/16863491.htmlmisc复杂度分析Dinic的复杂度上界为\(O(n^2m)\)。但是特殊情况下会更快,如二分图匹配是\(O(m\sqrtn)\)的;确定流量上限\(f\)时,复杂度为\(O(mf)\)。最小费用最大流的复杂度上界为\(O(nmf)\)。......
  • 图论提高
    复健\(Day7\)图论一\(DGA:\)有向无环图 \(SCC:\)强连通 \(BCC:\)双连通强联通:有向图中,两个顶点至少存在一条路径(两个顶点可以互相到达)强连通图:每两个顶点都强连通的有向图强连通分量:有向图的极大强连通子图\(1.\)有向图的强连通分量问题模型:对于一些存在依赖关系的模型,若......
  • 最近公共祖先(LCA)
    「观前提醒」「文章仅供学习和参考,如有问题请在评论区提出」目录前言定义性质求LCA倍增算法Trajan算法树链剖分基本概念基本性质具体实现参考资料前言简单的模板整理,只是概括了一下具体的实现方法(说到底是给自己写的),如果看不明白可以去看原视频(讲的很好),链接在参考资料里......
  • 进程注入检测 —— RtlCaptureStackBackTrace 获取当前函数的调用栈函数
    https://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64 https://cpp.hotexamples.com/examples/-/-/RtlCaptureStackBackTrace/cpp-rtlcapturestackbacktrace-function-examples.html  例子参考  平日里用VS开发工具在调时在Debug下有一个选......