HDU 2586 How far away (LCA模板题 树上点对间距离)

题目链接

HDU2586

题目大意

给定一个n(n 40000)个结点的带权无根树,有m(m 200)个询问,询问两点之间的距离。

分析

一看到题目最直观的感觉是一个最短路问题,但看数据规模,如果对于每一次询问都跑一遍最短路,肯定TLE,因此不能这么做。
由于,它是n个结点,n-1条边的图,因此是一棵树,所以要往树的算法上思考。树上两点之间的距离就是两个点到它们最近公共祖先的距离之和,于是问题转化为LCA问题。如果我们求出每一个结点到根节点的距离dis[i],则结点u到结点v的距离dist(u,v)=dis[u]+dis[v]-2*dis[LCA(u,v)].
dis[]可以在DFS的过程中求得。

代码

这里用基于RMQ的在线算法求LCA。

#include <iostream>
#include <cstdio>
#include <cstring>
const int MAXN=40010;
const int MAXM=80010;
using namespace std;
struct Edge
{
    int to,next,w;
}e[MAXM];

int vs[2*MAXN],depth[2*MAXN],id[MAXN],dis[MAXN],dmin[2*MAXN][17],cnt;
int edgenum,n,m,head[MAXN];
bool vis[MAXN];
void Add_edge(int u,int v,int w)
{
    e[++edgenum].to=v;
    e[edgenum].w=w;
    e[edgenum].next=head[u];
    head[u]=edgenum;
}
void dfs(int u,int d)///u为当前访问到的结点,d为深度
{
    vis[u]=true;
    vs[++cnt]=u;///vs[]为深度优先访问树的完整路径(包含2*n-1个元素) cnt为时间戳
    id[u]=cnt;///id[u]为u结点首次被访问时的时间戳
    depth[cnt]=d;///depth[u]为u结点的深度
    for (int t=head[u];t!=-1;t=e[t].next)
    {
        int v=e[t].to;
        int w=e[t].w;
        if (!vis[v])
        {
            dis[v]=dis[u]+w;
            dfs(v,d+1);
            vs[++cnt]=u;
            depth[cnt]=d;
        }
    }
}
void RMQ_Init(int n)///dmin[]数组维护的是depth[]最小值对应下标
{
    for (int i=1;i<=n;i++)
        dmin[i][0]=i;
    for (int j=1;(1<<j)<=n;j++)
        for (int i=1;i+(1<<j)-1<=n;i++)
        {
            int a=dmin[i][j-1];
            int b=dmin[i+(1<<(j-1))][j-1];
            if (depth[a]<depth[b])
                dmin[i][j]=a;
            else
                dmin[i][j]=b;
        }
}
int RMQ_min(int L,int R)
{
    int k=0;
    while ((1<<(k+1))<=R-L+1) k++;
    int a=dmin[L][k];
    int b=dmin[R-(1<<k)+1][k];
    if (depth[a]<depth[b])
        return a;
    else
        return b;
}
int LCA(int u,int v)
{
    int x=id[u],y=id[v];
    if (x>y) swap(x,y);
    int res=RMQ_min(x,y);
    return vs[res];
}

int main()
{
    int T,i,u,v,w,x,y;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        edgenum=0;cnt=0;
        memset(head,-1,sizeof(head));
        memset(id,0,sizeof(id));
        memset(vs,0,sizeof(vs));
        memset(dis,0,sizeof(dis));
        memset(depth,0,sizeof(depth));
        memset(vis,false,sizeof(vis));
        for (i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            Add_edge(u,v,w);
            Add_edge(v,u,w);
        }
        dfs(1,0);
        RMQ_Init(2*n-1);
        for (i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",dis[x]+dis[y]-2*dis[LCA(x,y)]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值