2017百度之星初赛B-1002(HDU-6115)

本文介绍了一种求解树状结构中任意两点间最短距离的方法,利用节点深度及父节点信息,通过调整节点位置寻找最低公共祖先(LCA),进而高效计算距离。适用于办公地点分布问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、思路

这题“看似”比较难搞的一点是,一个节点上有多个办公室,这怎么求?其他的,求树中任意两个节点的距离(注意:没有最远或最最进这一说法,因为树上任意两个节点之间有且仅有一条路径。不然就有回路了,对吧。)都不是特别难的问题。

(1)如何找到任意两个公司之间的最短距离?没有很好的办法,只能暴力。记录每个公司的每个办公室所在节点,求指定两个公司A和B的最短距离时,枚举公司A的所有办公室所在节点,再枚举公司B的所有办公室所在节点,求出被枚举的节点之间的距离,然后,取最小的那一个输出。

(2)求树中任意两个节点的距离?我一开始用的是Tarjan求LCA的算法,然后,交上去TLE了两发。后来换了一种策略,记录每个节点的深度、它的父节点以及它到父节点之间的距离。求任意两个点的LCA的时候,采用谁深度大谁先上移的方法,然后移动过程中记录移动了多少距离,当两个点移动到一个点时,这个点就是他们的LCA,返回记录的距离即可。这样,可以避免求子节点到根节点的距离(即麻烦又要多写很多不必要的代码)。另外一点,要不要先求出树的重心来优化时间复杂度,我用第二种策略提交的第一份AC的代码,求了树重心,耗时:8595MS;后来直接把求重心的代码去掉,直接把1号节点当总根节点,耗时:8782MS,差不多。所以,我下面贴的代码就没求树的重心了,这样代码也简洁了好多,关键函数就只有一个:找到给定两个节点的LCA并返回它们之间的距离。

二、代码

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int INF = 0x3fff3fff;
const int MAXN = 100010;
typedef struct {
    int to, wt, next;
} Edge;
Edge tree[MAXN * 2];
int head[MAXN], cnt;
int n, m, q;
/**office[i][j]:公司i的第j个办公室所在节点。*/
vector<int> office[MAXN];

void add(int from, int to, int wt) {
    tree[cnt].to = to;
    tree[cnt].wt = wt;
    tree[cnt].next = head[from];
    head[from] = cnt++;
}

void init() {
    memset(head, -1, sizeof(head));
    cnt = 0;
    for(int i = 0; i < MAXN; ++i)office[i].clear();
}

/** deep[i]:i节点的深度。
    f[i]:i节点的父节点。
    len2f[i]:i节点到父节点的长度。
*/
int deep[MAXN], f[MAXN], len2f[MAXN];
/**
    d:深度。总根(树的重心)节点的深度为0。
*/
void dfsDep(int root, int par, int d) {
    deep[root] = d;
    f[root] = par;
    for(int i = head[root], to = -1; i != -1; i = tree[i].next) {
        to = tree[i].to;
        if(to != par) {
            len2f[to] = tree[i].wt;
            dfsDep(to, root, d + 1);
        }
    }
}

int getDis(int a, int b) {
    int res = 0;
    while(deep[a] < deep[b]) {
        res += len2f[b];
        b = f[b];
    }
    while(deep[a] > deep[b]) {
        res += len2f[a];
        a = f[a];
    }
    while(a != b) {
        res += len2f[a] + len2f[b];
        a = f[a];
        b = f[b];
    }
    return res;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    int t, a, b, c;
    scanf("%d", &t);
    while(t--) {
        init();
        scanf("%d%d", &n, &m);
        for(int i = 1; i < n; ++i) {
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
            add(b, a, c);
        }
        for(int i = 1; i <= m; ++i) {
            scanf("%d", &a);
            for(int k = 0; k < a; ++k) {
                scanf("%d", &b);
                office[i].push_back(b);
            }
        }

        dfsDep(1, -1, 0);

        scanf("%d", &q);
        for(int i = 1; i <= q; ++i) {
            scanf("%d%d", &a, &b);
            c = INF;
            for(int j = 0, asz = office[a].size(); j < asz; ++j) {
                for(int k = 0, bsz = office[b].size(); k < bsz; ++k) {
                    c = min(c, getDis(office[a][j], office[b][k]));
                    if(c == 0) {j = asz; break;}
                }
            }
            printf("%d\n", c);
        }
    }
    return 0;
}

 

 


 

转载于:https://www.cnblogs.com/fuzhihong0917/p/7475058.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值