How far away ? 【LCA树上倍增】

题目链接HDU-2586

## **题目描述**:

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this “How far is it if I want to go from house A to house B”? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path(“simple” means you can’t visit a place twice) between every two houses. Yout task is to answer all these curious people.
就是给你一颗树,然后问任意两点的距离是多少;

思路

一开始看到数据范围只有40000,想着爆搜也不会T掉,然后就写了一发爆搜,然后就过了;

爆搜代码

#include <bits/stdc++.h>
using namespace std;

#define MAX_N 40010

int head[MAX_N], cnt = 0, vis[MAX_N], sum = 0;
int n, m;

struct Node{
    int to, val, next;
}edge[MAX_N * 2];

void init()
{
    memset(head, -1, sizeof(head));
    cnt = sum = 0;
}

void add(int x, int y, int val)
{
    edge[cnt].to = y;
    edge[cnt].val = val;
    edge[cnt].next = head[x];
    head[x] = cnt++;
}

int dfs(int x, int y)
{
    for(int i = head[x]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == y)
        {
            return edge[i].val;
        }
        else
        {
            if(!vis[v])
            {
                vis[v] = 1;
                int k = dfs(v, y);
                if(k)
                    return edge[i].val + k;
            }
        }
    }
    return 0;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        init();
        cin >> n >> m;
        for(int i = 0; i < n - 1; i++)
        {
            int x, y, val;
            cin >> x >> y >> val;
            add(x, y, val);
            add(y, x, val);
        }
        while(m--)
        {
            memset(vis, 0, sizeof(vis));
            int x, y;
            cin >> x >> y;
            vis[x] = 1;
            cout << dfs(x, y) << endl;
        }
    }
    return 0;
}

爆搜的意思就是从x点出发,能不能到达y点,可以到达则返回到达y点的距离,不能到达那就返回0,由于是无向图,所以加了一个vis数组记录节点是否已经访问;可能数据比较弱吧,然后就是用LCA的解法;

LCA是用来求解最近公共祖先的,在这道体中,我们可以在dfs预处理的时候把每个节点到根节点的距离也预处理出来;就是 Dist[v] = Dist[u] + edge[i].val;
这样子;
然后画张图看一下;
在这里插入图片描述
若我们要求4到6的距离,则只需要求出4,6的最近公共祖先,也就是3;
然后Dist[4] + Dist[6] - 2 * Dist[3] 就是4到6的距离了;很好理解的;

LCA代码


#include <bits/stdc++.h>
using namespace std;

#define MAX_N 500050
#define MAX_M 500050

int head[MAX_N], cnt = 0, fa[MAX_N][30], dfn[MAX_N], Dist[MAX_N];
int n, m, t;

struct Node{
    int to, val, next;
}edge[MAX_M * 2];

void init()
{
    cnt = 0;
    memset(head, -1, sizeof(head));
    memset(dfn, 0, sizeof(dfn));
}

void add(int x, int y, int val)
{
    edge[cnt].to = y;
    edge[cnt].val = val;
    edge[cnt].next = head[x];
    head[x] = cnt++;
}

void dfs(int u)
{
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(!dfn[v])
        {
            Dist[v] = Dist[u] + edge[i].val;
            dfn[v] = dfn[u] + 1;
            fa[v][0] = u;
            dfs(v);
        }
    }
}

int lca(int x, int y)
{
    if(dfn[x] < dfn[y])
    {
        swap(x, y);
    }
    for(int i = 20; i >= 0; i--)
    {
        if(dfn[fa[x][i]] >= dfn[y])
        {
            x = fa[x][i];
        }
    }
    if(x == y)
        return x;
    for(int i = 20; i >= 0; i--)
    {
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d", &n, &m);
        init();
        for(int i = 0; i < n - 1; i++)
        {
            int x, y, val;
            scanf("%d %d %d", &x, &y, &val);
            add(x, y, val);
            add(y, x, val);
        }
        dfn[1] = 1, fa[1][0] = 0, Dist[1] = 0;
        dfs(1);
        for(int i = 1; i <= 20; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                fa[j][i] = fa[fa[j][i-1]][i-1];
            }
        }
        while(m--)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            printf("%d\n", Dist[x] + Dist[y] - 2 * Dist[lca(x, y)]);
        }
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值