HDU 2586

题意很简单,给一棵树,给你已知的每条边的权值,输入一个起点 s 一个重点 t 求二者间距离。

 

思路很简单,要想从 a 跑到 b  只需要找到二者的 最近公共祖先 lca,之后把 根到 i 节点 距离设为 dis[i] ,

最终结果 就是 dis[s] +dis[t] - dis[lca] * 2

 

那么问题来怎么求 lca 呢?

首先 我们想一下最暴力的方法

对于这样一张图 我们该如何去找 lca 呢? 

找到 s 找到 t 然后两个人一点点找父节点,当然 对图分一下层,避免 s 或者 t 在找 lca 时 找到了 lca 上一层的节点,这样肯定是出问题的,所以 我们开一个 deep 数组来记录深度,避免找过头。

之后 暴力向上找就可以了吧。。。。

但是 这样做。。如果情况够差,很明显耗时 太长了。。。不合适。。

 

那么我们有没有一些优化的方法?

那就是 倍增法

开一个数组 p[x][i] ,这个数组表示标号为x节点向上跳2^i步的节点。例如grand[5][0]=2,因为5这个节点向上跳2^0次方个就是2,当然所有节点向上跳一个就是他的父亲节点,那grand[x][1]=什么呢?等于p[ p[x][0] ][ 0 ];

那么这样的话 我们再从高到低依次跳上去,不就节省了很多时间吗。

套用网络大犇博客上写的一些规律(ground = p)

既grand[x][i]=grand[grand[x][i-1]][i-1],为什么呢,2^4=2^3+2^3,表示 x向上跳2的i次的节点=x向上跳i-1次方的这个节点向上跳i-1次方,有点绕口。例如 grand[13][2]=grand[6][2]=g[g[rand][i-1]][i-1];
 

当然 现在这么看还有个问题,如果 s t 不在一样的深度,这样跳会不会出现问题。

当然 肯定第一步 先得把两个节点深度调整成一致的,调整跟深度低的一样。

然后就两个节点一起向上跳一个2^j。

当然 我们来看代码:

首先是预处理,dfs 一下,确定深度和父节点还有权值

void dfs(int u,int pre,int t)
{
    int i;
    deep[u]=t;//深度
    f[u]=pre;//父节点
    for(i=0;i<e[u].size();i++)
    {
        int v=e[u][i].to;
        if(v!=pre)
        {
            dis[v]=dis[u]+e[u][i].w;//距离根的距离
            dfs(v,u,t+1);
        }
    }
}

之后 我们输入不同边的权值,最后根据已知建成的图 来更新一下父节点数组

void init()
{
    //p[i][j]表示i结点的第2^j祖先
    int i,j;
    for(j=0;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
            p[i][j]=-1;
    for(i=1;i<=n;i++)
        p[i][0]=f[i];
    for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
            if(p[i][j-1]!=-1)
                p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}

最后 输入 s t 找到 lca

int lca(int a,int b)//最近公共祖先
{
    int i,j;
    if(deep[a]<deep[b])
        swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    //使a,b两点的深度相同
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
        a=p[a][j];
    if(a==b)
        return a;
    //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return f[a];
}

感谢大牛们的思路和板子。。。。

 

 

之后 我们就可以开心的 A 掉这道题了(虽然以前听学长讲过LCA 但是很显然。。忘得有点快。。)

 

以下是 AC 代码

 

 

 

#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node
{
    int to,w;
    node(int a,int b)
    {
        to=a;
        w=b;
    }
};
vector<node>e[maxn];
int f[maxn],dis[maxn],deep[maxn];
int p[maxn][20];
int n,m;
void dfs(int u,int pre,int t)
{
    int i;
    deep[u]=t;//深度
    f[u]=pre;//父节点
    for(i=0;i<e[u].size();i++)
    {
        int v=e[u][i].to;
        if(v!=pre)
        {
            dis[v]=dis[u]+e[u][i].w;//距离根的距离
            dfs(v,u,t+1);
        }
    }
}
void init()
{
    //p[i][j]表示i结点的第2^j祖先
    int i,j;
    for(j=0;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
            p[i][j]=-1;
    for(i=1;i<=n;i++)
        p[i][0]=f[i];
    for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
            if(p[i][j-1]!=-1)
                p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
    int i,j;
    if(deep[a]<deep[b])
        swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    //使a,b两点的深度相同
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
        a=p[a][j];
    if(a==b)
        return a;
    //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return f[a];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            e[i].clear();
        for(int i=1;i<n;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back(node(b,c));
            e[b].push_back(node(a,c));
        }
        dis[1]=0;
        dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
        init();     //初始各个点的2^j祖先是谁
        for(int i=0;i<m;i++)
        {
            int a,b,ans;
            scanf("%d%d",&a,&b);
            ans=dis[a]+dis[b]-2*dis[lca(a,b)];
            printf("%d\n",ans);
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值