基于RMQ的LCA

最近公共祖先问题(LCA)就是:

给定一棵树,不断地询问两个点的最近的公共祖先节点是多少。

基于RMQ的方法是一种在线的方法,基本思想是

对树进行DFS,求出树的DFS序,记录下每个节点第一次访问时在DFS序中的位置,我们可以知道:

所询问的两个点的最近公共祖先是这两个节点在DFS序中两个下标的区间内离根最近的那个节点。

所以我们在进行DFS是还需要记录下每个节点的深度信息并和DFS序关联上

由于上面需要求解一个区间中深度最小(离根最近)的节点,而且要询问很多次。一棵树地DFS序是确定的,所以我们可以对区间最小值问题进行一下ST表预处理,预处理复杂度是 O(nlogn) ,查询的复杂度是 O(1)
需要注意的是我们要得到的不是这个区间中深度最小的点的深度信息,而是这个节点的编号,我们的做法是用下标将DFS序和深度信息关联上,这样RMQ返回的将不是最小的深度而是最小深度所对应的下标,由下标我们可以得到这个节点的编号(DFS序中对应于下标的值)

这里写图片描述
该树的DFS序是:1 2 4 2 5 7 8 5 2 1 3 6 3 1
DFS序包含回溯之后的节点

LCA模板题 HDU 2586

  • 题目大意

    有n个村庄,村庄之间有n-1条边,边上有一个权值w,现在给出m组询问,询问两个节点之间的距离是多少( 2n40000,1m200 )

  • 分析

    由于n比较大所以不能用最短路的方法来求最短距离,题目又表明是一颗树。
    两个节点之间的最短距离就是这两个节点到他们的最近公共祖先的距离之和,问题就转化成了LCA问题。
    求出每个节点到根节点的距离,问题的答案就是:节点a到根节点的距离+节点b到根节点的距离-2*节点LCA(a,b)到根节点的距离。
    需要注意的是由于树上的边是带权的,所以LCA中节点的深度信息depth表示的不再是深度而是节点到根节点的距离。

  • 代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
int T,n,m;//n个村庄,m次询问
const int MAXN=40005;//最大的村庄个数
struct Edge
{
      int v;
      int w;
      int next;
}edge[2*MAXN];
int edgecount;
int head[MAXN];
void Add_edge(int u,int v,int w)
{
      edge[++edgecount].v=v;
      edge[edgecount].w=w;
      edge[edgecount].next=head[u];
      head[u]=edgecount;
}
void In()
{
    int u,v,k;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&k);
        Add_edge(u,v,k);
        Add_edge(v,u,k);
    }
}
void Init()
{
      edgecount=0;
      memset(head,-1,sizeof(head));
}
int vs[MAXN*2];//dfs访问的顺序,相当于时间戳
int depth[MAXN*2];//节点的深度
int id[MAXN*2];//各个顶点在vs中首次出现的下标
void Dfs(int u,int p,int d,int &x)
{
    id[u]=x;
    vs[x]=u;
    depth[x++]=d;
    for(int k=head[u];k!=-1;k=edge[k].next)
    {
          int v=edge[k].v;
          int w=edge[k].w;
          if(v!=p)
          {
                Dfs(v,u,d+w,x);
                vs[x]=u;
                depth[x++]=d;
          }
    }
    return ;
}
int dp[MAXN*2][20];
void RMQ_init(int n)//预处理数组a中从1到n的区间最小值
{
    for(int i=1;i<=n;i++)dp[i][0]=i;
    for(int j=1;(1<<j)<=n;j++)
    {
          for(int i=1;i+(1<<j)-1<=n;i++)
          {
                int x=dp[i][j-1];
                int y=dp[i+(1<<j-1)][j-1];
                if(depth[x]<depth[y])dp[i][j]=x;
                else dp[i][j]=y;
          }
    }
}
int Query(int L,int R)
{
      int k=log((double)(R-L+1))/log(2.0);
      int x=dp[L][k];
      int y=dp[R-(1<<k)+1][k];
      if(depth[x]<depth[y])return x;
      else return y;
}
void LCA()
{
    int root=1;
    int k=1;
    Dfs(root,-1,0,k);
    RMQ_init(n*2);
}
int main()
{
    int a,b;
    scanf("%d",&T);
    while(T--)
    {
        Init();
        In();
        LCA();
        for(int i=1;i<=m;i++)
        {
             scanf("%d%d",&a,&b);
             int x=Query(min(id[a],id[b]),max(id[a],id[b]));
             cout<<depth[id[a]]+depth[id[b]]-2*depth[id[vs[x]]]<<endl;
        }
    }
    return 0;
}
/*
1
8 13
1 2 1
1 3 1
2 4 1
2 5 1
5 7 1
5 8 1
3 6 1


4 8
4 6
7 8
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值