【总结】LCA的4种求法

前言

LCA的求法有多重多样,总结下来是下面这4种.希望大家可以加油!

暴力求LCA

我们考虑dfs求出每一个点的父亲(在当前根下),然后直接先暴力跳到同一个深度,再同时跳

void dfs(int u,int f){
    fa[u]=f;dep[u]=dep[f]+1;
    for(re int i=front[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==f)continue;
        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];
    while(u!=v)u=fa[u],v=fa[v];
    return u;
}

倍增求LCA

我们考虑每一次跳1个父亲的速度太慢,那么怎么优化呢?

这个时候就需要用到倍增这种思想了.

没有学过倍增的同学可以先写一下ST表,可能会对倍增有比较深刻的理解.

我们假设这样子一个变量\(f[i][j]\)表示点\(i\)的第\(2^j\)个父亲是哪个节点.

因为每一个数都可以二进制表示,所以我们考虑每一次从大到小枚举跳的东西,然后就可以做到\(\Theta(n\ log(n))\)

void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    for(re int i=front[u];i;i=nxt[i]){
        int v=to[i];
        if(v!=fa)dfs(v,u),f[0][v]=u;
    }
}
int lca(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    for(re int i=20;~i;i--)
        if(dep[a]-(1<<i)>=dep[b])
            a=f[i][a];
    if(a==b)return a;
    for(re int i=20;~i;i--)
        if(f[i][a]!=f[i][b])
            a=f[i][a],b=f[i][b];
    return f[0][a];
}

树链剖分求LCA

考虑把一个树分成轻链与重链,然后直接跳链就好了.

void dfs1(int u,int f){
    fa[u]=f;siz[u]=1;dep[u]=dep[f]+1;
    for(re int i=front[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa[u])continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]])son[u]=v;
    }
}
void dfs2(int u,int tp){
    top[u]=tp;
    if(!son[u])return;
    dfs2(son[u],tp);
    for(re int i=front[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa[u] || v==son[u])continue;
        dfs2(v,v);
    }
}
void swap(int &a,int &b){
    int tmp=a;a=b;b=tmp;
}
int lca(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        u=fa[top[u]];
    }
    return dep[u]>dep[v]?v:u;
}

Tarjan求LCA

考虑把每一个询问当做一条边处理,那么如果这两个都被访问了,显然另一个点的祖先一定是他们的LCA.

所以可以很容易地写出这一段代码.(注意最后合并)

int find(int x){
    if(f[x]!=x)f[x]=find(f[x]);
    return f[x];
}
void Add(int u,int v){
    to[++cnt]=v;nxt[cnt]=front[u];
    front[u]=cnt;
}
void Addques(int u,int v,int Id){
    toq[++cnt]=v;
    id[cnt]=Id;nxtq[cnt]=frontq[u];
    frontq[u]=cnt;
}
void dfs(int u,int fa){
    b[u]=1;
    for(int i=front[u];i;i=nxt[i]){
        int v=to[i];
        if(v!=fa){
            dfs(v,u);
            for(int j=frontq[v];j;j=nxtq[j]){
                int vv=toq[j];
                if(b[vv])ans[id[j]]=find(vv);
            }
            int uu=find(u),vv=find(v);
            if(uu!=vv)f[vv]=uu;
        }
    }
}

原文地址:https://www.cnblogs.com/cjgjh/p/9800148.html

时间: 2024-10-09 20:46:59

【总结】LCA的4种求法的相关文章

LCA的两种求法

HDU 2586 题意:一棵树,多次询问任意两点的路径长度. LCA:最近公共祖先Least Common Ancestors.两个节点向根爬,第一个碰在一起的结点. 求出x, y的最近公共祖先lca后,假设dist[x]为x到根的距离,那么x->y的距离为dist[x]+dist[y]-2*dist[lca] 求最近公共祖先解法常见的有两种 1, tarjan+并查集 2,树上倍增 首先是树上倍增. 1 #include <cstdio> 2 #include <cstring&

【模板】lca的几种求法

1,倍增 vector<int>p[maxn]; int dep[maxn],f[maxn][20];//f[i][0]保存i的父亲 inline void dfs(int u,int fa,int d) { dep[u]=d;f[u][0]=fa; for(int i=0;i<p[u].size();i++) { int v=p[u][i]; if(v==fa)continue; dfs(v,u,d+1); } } inline void init(int n) { for(int j

逆序数的几种求法

逆序数的几种求法 白话经典算法系列之九 从归并排序到数列的逆序数对(微软笔试题)

LCS的几种求法

\(LCS:\) 对于两个长度均为 \(N\) 的数列 \(A\) 和 \(B\) ,存在一个数列 \(C\) 使得 \(C\) 既是 \(A\) 的子序列有事 \(B\) 的子序列,现在需要求这个数列的最长的长度,这就是最长公共子序列. \(solution\quad 1:\) 这道题是世界上最经典的DP题之一,我们可以知道我们做需要求的子序列中的任意一个元素在 \(A\) 和 \(B\) 中都存在,所以我们可以设出状态即 \(F[i][j]\) 表示我们已经匹配了 \(A\) 的前 \(i\

LCA的五种解法

标准求法 //O(nlogn)-O(logn) #include<cstdio> #include<algorithm> using namespace std; const int maxn=100010; int first[maxn],next[maxn*2],to[maxn*2],dis[maxn*2]; int n,m; void AddEdge(int a,int b,int c) { to[++m]=b; dis[m]=c; next[m]=first[a]; fir

求 LCA 的三种方法

(YYL: LCA 有三种求法, 你们都知道么?) (众神犇: 这哪里来的傻叉...) 1. 树上倍增 对于求 LCA, 最朴素的方法是"让两个点一起往上爬, 直到相遇", "如果一开始不在同一深度, 先爬到同一深度". 树上倍增求 LCA 的方法同样基于这个道理, 只不过利用了倍增思想从而加速了"向上爬"的操作. 也就是说, 每次向上爬的高度不是 1, 而是 2 的幂. 我们用 $f(i, j)$ 表示从节点 $i$ 向上爬 $2^j$ 的高度

LCA的两种写法

第一种是离线的Tarjan算法 #include<cstdio> using namespace std; int rd(){ int x=0,fl=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){fl=-1;}ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} re

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度. 题解 这题的描述很短,给人一种很可做的假象. 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq. 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在pa

集合并卷积的三种求法

也许更好的阅读体验 本文主要内容是对武汉市第二中学吕凯风同学的论文<集合幂级数的性质与应用及其快速算法>的理解 定义 集合幂级数 为了更方便的研究集合的卷积,引入集合幂级数的概念 集合幂级数也是形式幂级数的一种,只是集合的一种表现形式,无需考虑收敛或发散的含义 定义一个集合 \(S\) 的集合幂级数为 \(f\) ,那么我们就可以把集合 \(S\) 表示为如下形式 \(\begin{aligned}f=\sum _{T\subseteq S}f_{T}\cdot x^{T}\end{align