LCA树链剖分算法——算法解析

算法简介:

前段时间为了一道LCA的题目伤透了脑筋,一直都只过了8组数据,另外两组数据总是超时。因为题目的特性,在这里用tarjan离线的方法求LCA并不是个明智的选择反而会使问题变得更加复杂。于是我就在网上自学了一下树链剖分的LCA求法,主要思想是利用轻重链的划分来快速在树上进行跳转。

算法分析:

1.按照深度优先遍历一遍整棵树,对于每个结点,选取它的子节点中以这个子节点为根的子树 s i z e size size最大的那一个,将这个子节点和他的父节点连接起来,表示他们在同一条重链上。
2.在查询两个节点的LCA时,如果当前两个节点不在同一重链上,则进行跳转(先跳深度更大的那个)。
3.重复上面操作 2 2 2直到两个节点处于同一重链,这时候因为两点在同一条链上,则返回深度更低的那一个,即两个点的LCA。

模板程序:

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;
const int MAXN=300000;
int n,m,t=0;
int p[MAXN],d[MAXN],size[MAXN],son[MAXN],top[MAXN],fa[MAXN];  
struct node
{
    int v;
	int next;
}e[MAXN*2];  
void insert(int u,int v)
{  
	e[t].v=v;
	e[t].next=p[u]; 
    p[u]=t++;  
}  
void dfs1(int x)
{  
    size[x]=1;
    for(int i=p[x];i!=-1;i=e[i].next)  
    {
        int v=e[i].v;  
        if(fa[x]!=v)
		{
            fa[v]=x;  
            d[v]=d[x]+1;  
            dfs1(v);  
            size[x]+=size[v];  
            if(size[son[x]]<size[v])//son[i]表示结点i的儿子中以其为根的子树size最大的那个(用来构造重链)
		        son[x]=v;
        }  
    }  
}  
void dfs2(int x)
{  
    if(x==son[fa[x]])//如果x是fa[x]的儿子中拥有最大size的那个
        top[x]=top[fa[x]];//把top[x]连向top[fa[x]](形成重链)   
    else
		top[x]=x;//否则指向它自己   
    for(int i=p[x];i!=-1;i=e[i].next)
        if(e[i].v!=fa[x])
          dfs2(e[i].v);
}
int LCA(int u,int v)
{  
    while(top[u]!=top[v])//u、v不在同一条重链上时
	{
		if(d[top[u]]>d[top[v]])//将深度大的上提
        	u=fa[top[u]];
		else
			v=fa[top[v]];
    }
    if(d[u]<d[v])//返回u、v中在较上方的那个
    	return u;
    return v;
}
int main()
{
    memset(p,-1,sizeof(p));
    scanf("%d%d",&n,&m);   
    int root;
	bool isNotRoot[MAXN];  
    for(int i=1;i<n;i++)
	{  
        int u,v;  
        scanf("%d%d",&u,&v);  
        isNotRoot[v]=1;
        insert(u,v);
        insert(v,u); 
    }
    for(root=1;root<=n;root++)
      	if(!isNotRoot[root])
	  		break;   
    d[root]=1;   
    dfs1(root);//预处理d和fa   
    dfs2(root);//预处理top 
    for(int i=1;i<=m;i++)
	{  
        int u,v;  
        scanf("%d%d",&u,&v);  
        printf("%d\n",LCA(u,v));
    }
    
    return 0;
}

如果大家理解了的话就可以去做几道题练一下手啦!下面推荐几道题:
BZOJ 1832,3612,NOIP2015 运输计划

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值