树链剖分求解lca

树链剖分:
用于将树分割成若干条链的形式,以维护树上路径的信息。
树链剖分都指"重链剖分"
重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。

首先明确概念:
重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
轻儿子:父亲节点中除了重儿子以外的儿子;
重边:父亲结点和重儿子连成的边;
轻边:父亲节点和轻儿子连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;
在这里插入图片描述
树中只有重子节点和轻子节点。

#include <bits/stdc++.h>

#define ll long long


using namespace std;
int next[500001];
int head[500001];
int to[500001];
int cnt;

int son[500001];//求重儿子
int fa[500001];//父亲结点
int tol[500001];//子节点个数
int dep[500001];//深度

int dfn[500001];//dfs序列
int rnk[500001];//dfs序列所对应的元素
int top[500001];//链的顶端
//链式前向星
void add(int u,int v)
{
    next[++cnt]=head[u];
    head[u]=cnt;
    to[cnt]=v;
}
//第一个dfs求四个  son重儿子  tol儿子结点数
//dep深度  fa 父节点
void dfs1(int u,int v)//父亲结点 自己
{
    son[v]=-1;
    tol[v]=1;//本身自己节点
    dep[v]=dep[u]+1;
    fa[v]=u;
    for (int i=head[v];i;i=next[i])
    {
        int t=to[i];
        if(t==u) continue ;
        dfs1(v,t);
        tol[v]+=tol[t];
        if(son[v]==-1||(tol[t]>tol[son[v]])) son[v]=t;
        //条件1  还未求该节点的重儿子
        //条件2  目前求得的儿子的子节点个数大于之前求得的个数
    }
}
//第二个dfs求出每条链的顶端节点 用top记录
//记录dfs序 和dfs序值所对应的节点
void dfs2(int u,int v) //当前节点  重链的顶端
{
    //重链
    top[u]=v;
    dfn[v]=cnt++;
    rnk[cnt]=u;
    if(son[u]==-1)
    return ;
    dfs2(son[u],v);

    //轻链
    for (int i=head[u];i;i=next[i])
    {
        if(to[i]!=son[u]&&to[i]!=fa[u]) dfs2(to[i],to[i]);
    }
}

int lca(int a,int b)
{

    while(top[a]!=top[b]) //不在一条链上的时候 顶端不一样就不再一条链上
    {

        if(dep[top[a]]>dep[top[b]]) //a位于的链的顶端深度高于b位于的
        {
            a=fa[top[a]];  //a就往上跳
        }
        else{
            b=fa[top[b]];
        }
    }
    if(dep[a]>dep[b])
        return b;
    else return a;
}

int main()
{
    int n,m,s;
    cin>>n>>m>>s;
    for (int i=0;i<n-1;i++)
    {
        int a,b;
        cin>>a>>b;
        add(b,a);
        add(a,b);
    }
    dfs1(s,s);
    dfs2(s,s);
    for (int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        cout<<lca(a,b)<<endl;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值