蓝桥杯真题 - 景区导游 - 题解

题目链接:https://www.lanqiao.cn/problems/3516/learning/

个人评价:难度 4 星(满星:5)
前置知识:LCA


整体思路

  • 假设能快速算出任意两个节点 u , v u,v u,v 之间的距离,就可以先算一遍所有 A i A_i Ai A i + 1 A_{i+1} Ai+1 之间的距离总和,记为 d i s S u m = ∑ i = 1 i = K − 1 d i s A i , A i + 1 disSum=\sum_{i=1}^{i=K-1} dis_{A_i,A_{i+1}} disSum=i=1i=K1disAi,Ai+1,然后考虑跳过每一个 A i   ( 2 ≤ i ≤ K − 1 ) A_i~(2 \leq i \leq K-1) Ai (2iK1) 节点的情况:把 d i s S u m disSum disSum 减去 ( d i s A i − 1 , A i + d i s A i , A i + 1 ) (dis_{A_{i-1},A_i} + dis_{A_i,A_{i+1}}) (disAi1,Ai+disAi,Ai+1),再加上 d i s A i − 1 , A i + 1 dis_{A_{i-1},A_{i+1}} disAi1,Ai+1 就是跳过节点 A i A_i Ai 的结果(当 i = 1 i=1 i=1 i = K i=K i=K 时直接把 d i s S u m disSum disSum 分别减去 d i s A 1 , A 2 dis_{A_1,A_2} disA1,A2 d i s A K − 1 , A K dis_{A_{K-1},A_K} disAK1,AK 即可);
  • 先用一个 dfs 跑出所有节点 u u u 到根节点的距离 d i s u , r o o t dis_{u,root} disu,root,然后任意两个节点之间的距离,就是它们到根节点之间的距离总和减去两倍的它们最近公共祖先到根节点的距离,假设用 LCA 求出最近公共祖先节点为 f f f,则任意两点间距离为

d i s u , v = d i s u , r o o t + d i s v , r o o t − 2 × d i s f , r o o t dis_{u,v}=dis_{u,root}+dis_{v,root}-2\times dis_{f,root} disu,v=disu,root+disv,root2×disf,root

过题代码

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

typedef long long LL;
const int Log = 20;
const int maxn = 100000 + 100;
struct Node {
    int pos;
    LL dis;
    Node() {}
    Node(int pos, LL dis) : pos(pos), dis(dis) {}
};

int n, k, u, v, t, idCnt;
int st[maxn][Log];
LL disSum;
vector<Node> G[maxn];
int id[maxn], a[maxn];
LL dis[maxn], disPre[maxn];

void dfs(int root, int fa) {
    id[root] = ++idCnt;
    st[root][0] = fa;
    for(int j = 1; j < Log; ++j) {
        st[root][j] = st[st[root][j - 1]][j - 1];
    }
    for (Node &node: G[root]) {
        int pos = node.pos;
        if (pos != fa) {
            dis[pos] = dis[root] + node.dis;
            dfs(pos, root);
        }
    }
}

int lca(int x, int y) {
    if(x == y) {
        return x;
    }
    if(id[x] > id[y]) {
        swap(x, y);
    }
    for(int i = Log - 1; i >= 0; --i) {
        if(id[st[y][i]] > id[x]) {
            y = st[y][i];
        }
    }
    return st[y][0];
}

int main() {
#ifdef ExRoc
    freopen("test.txt", "r", stdin);
#endif // ExRoc
    ios::sync_with_stdio(false);

    cin >> n >> k;
    for (int i = 1; i < n; ++i) {
        cin >> u >> v >> t;
        G[u].push_back(Node(v, t));
        G[v].push_back(Node(u, t));
    }
    dfs(1, 1);
    for (int i = 1; i <= k; ++i) {
        cin >> a[i];
    }
    for (int i = 2; i <= k; ++i) {
        int f = lca(a[i], a[i - 1]);
        disPre[i] = dis[a[i]] + dis[a[i - 1]] - 2 * dis[f];
        disSum += disPre[i];
    }
    cout << disSum - disPre[2] << " ";
    for (int i = 1; i + 2 <= k; ++i) {
        int f = lca(a[i], a[i + 2]);
        cout << disSum - disPre[i + 1] - disPre[i + 2] + dis[a[i]] + dis[a[i + 2]] - 2 * dis[f] << " ";
    }
    cout << disSum - disPre[k] << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值