树链剖分求LCA

树链剖分求LCA,首先需要处理以下几个数组:

father[x]:x节点的父亲

son[x]:x节点的重儿子

size[x]:x节点子树的大小(节点个数)

dep[x]:x在树中的深度

top[x]:x所在重链的顶端节点

 

树链剖分需要两次dfs,第一次求出father、son、size和dep数组,第二次求出top数组。然后再求lca即可。

附上代码:

 

#include <iostream>
#include <cstdio>
#define maxn 1000050
using namespace std;

int n,m,s;
int head[maxn],cnt;
int fa[maxn],dep[maxn],siz[maxn],son[maxn];
int top[maxn];

struct EDGE
{
    int next;
    int to;
}edge[maxn];

void add(int u,int v)
{
    edge[++cnt].next=head[u];
    edge[cnt].to=v;
    head[u]=cnt;
}

void dfs_1(int root)
{
    siz[root]=1;
    son[root]=0;
    for(int i=head[root];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[root])continue;
        dep[v]=dep[root]+1;
        fa[v]=root;
        dfs_1(v);
        if(siz[son[root]]<siz[v]) son[root]=v;
        siz[root]+=siz[v];
    }
}

void dfs_2(int root,int tp)
{
    top[root]=tp;
    if(son[root]) dfs_2(son[root],tp);
    for(int i=head[root];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[root]||v==son[root])continue;
        dfs_2(v,v);     
    }
}

int lca(int u,int v)
{
    int fu=top[u],fv=top[v];
    while(fu!=fv)
    {
        if(dep[fu]<dep[fv])
        {
            swap(fu,fv);
            swap(u,v);
        }
        u=fa[fu];
        fu=top[u];
    }
    if(dep[u]<dep[v])swap(u,v);
    return v;
}

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    dep[s]=1;
    for(int i=1;i<=n-1;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs_1(s);
    dfs_2(s,s);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }
    return 0;
}

 

代码注释版(用于理解):

 

#include <iostream>
#include <cstdio>
#define maxn 1000050
using namespace std;

int n,m,s;
int head[maxn],cnt;
int fa[maxn],dep[maxn],siz[maxn],son[maxn];//fa[i]表示i结点父亲,dep[i]表示i结点深度,siz[i]表示i结点子树大小(子树结点数)
//son[i]表示i的重儿子 
int top[maxn];//top[i]表示i结点所在重链的顶部节点。叶节点的top是自己。 

struct EDGE
{
    int next;
    int to;
}edge[maxn];

void add(int u,int v)
{
    edge[++cnt].next=head[u];
    edge[cnt].to=v;
    head[u]=cnt;
}//链式前向星存图 

void dfs_1(int root)//root为当前节点 
{
    siz[root]=1;//root的子树大小初始化为1 
    son[root]=0;//root的重儿子初始化为0 

    for(int i=head[root];i;i=edge[i].next)//遍历以root为起点的每一条边 
    {
        int v=edge[i].to;//v是边的终点 
        if(v==fa[root])continue;//如果v是父亲,直接跳过 
        dep[v]=dep[root]+1;//更新v节点深度 
        fa[v]=root;//更新v节点父亲 
        dfs_1(v);//继续dfs 
        if(siz[son[root]]<siz[v]) son[root]=v;//找出子树最大的儿子,作为重儿子 
        siz[root]+=siz[v];//更新子树大小 
    }
}//第一遍dfs,预处理出fa,dep,siz,son数组 

void dfs_2(int root,int tp)//tp为当前节点所在重链顶部节点 
{
    top[root]=tp;//更新top 
    if(son[root]) dfs_2(son[root],tp); //如果可以,一直沿着重儿子走 
    for(int i=head[root];i;i=edge[i].next) 
    {
        int v=edge[i].to;
        if(v==fa[root]||v==son[root])continue; //如果遇到父亲或重儿子直接跳过 
        dfs_2(v,v); //继续更新 
    }
}//预处理top数组 

int lca(int u,int v)
{
    int fu=top[u],fv=top[v]; //fu为u节点所在重链的顶部节点,fv同fu 
    while(fu!=fv)//u,v不在一条重链上 
    {
        if(dep[fu]<dep[fv])
        {
            swap(fu,fv);
            swap(u,v);
        }
        u=fa[fu];
        fu=top[u];//选择深度较大的点往上跳,更新fu数组 
    }
    if(dep[u]<dep[v])swap(u,v);
    return v;//深度较浅的点就是u,v的lca 
}

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    dep[s]=1;
    for(int i=1;i<=n-1;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs_1(s);
    dfs_2(s,s);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }

    return 0;
}

 

posted @ 2019-05-08 00:28  Black_Gzombie  阅读(144)  评论(0编辑  收藏  举报