P10289 [GESP样题 八级] 小杨的旅游

Description

给定一棵 n n n 个点的树,每条边权值均为 1 1 1,树上有 k k k 个关键点,关键点们在 0 0 0 的时间内相互可达, q q q 次询问,求 s → t s\to t st 的最短路。

Analysis

考虑暴力建图,则图上共有 ( n − 1 + n ( n − 1 ) 2 ) (n-1+\frac{n(n-1)}{2}) (n1+2n(n1))条边,在 n , k n,k n,k 均为最大的情况下,图上共有大约 2 × 1 0 10 2 \times 10^{10} 2×1010 条边,铁定 MLE

我们注意到,从 s s s t t t 只有两种情况:

  • 老老实实从树上走;
  • s s s 走到一个关键点 k 1 k_1 k1,穿越到另一个关键点 k 2 k_2 k2,再走到 t t t

第一种情况就是常规树上两点最短路。
第二种情况,根据贪心思想,选取的 k 1 , k 2 k_1,k_2 k1,k2 一定是距离 s , t s,t s,t 最近的两个。所以我们初始时一次 bfs 求出每个点 i i i 到传送门的距离 d s t i dst_i dsti,最终答案即为 d s t s + d s t t dst_s+dst_t dsts+dstt

Code

// Problem: P10289 [GESP样题 八级] 小杨的旅游
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P10289
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <queue>
#include <cmath>
using namespace std;

using Graph = vector<vector<int>>;
const int INF = 1e9;

struct LCA{
    int n, k;
    vector<int> dep;
    vector<vector<int>> f;
    Graph G;
    
    LCA() {}
    LCA(const Graph &tree): G(tree){
        n = G.size();
        k = (int)log2(n) + 1;
        
        dep.assign(n, 0);
        f.assign(n, vector<int>(k, -1));
        dfs(0, 0);
    }
    
    void dfs(int u, int fa){
        dep[u] = dep[fa] + 1;
        f[u][0] = fa;
        
        for(int i = 1; i < k; i++) f[u][i] = f[f[u][i - 1]][i - 1];
        
        for(auto v: G[u]){
            if(v == fa) continue;
            dfs(v, u);
        }
    }
    
    int lca(int x, int y){
        if(dep[x] < dep[y]) swap(x, y);
        for(int i = k - 1; i >= 0; i--)
            if(dep[f[x][i]] >= dep[y]) x = f[x][i];

        if(x == y) return x;
        for(int i = k - 1; i >= 0; i --)
            if(f[x][i] != f[y][i]){
                x = f[x][i];
                y = f[y][i];
            }

        return f[x][0];
    }
    
    int dist(int x, int y){
        return dep[x] + dep[y] - 2 * dep[lca(x, y)];
    }
    
};



signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    
    int n, k, q;
    cin >> n >> k >> q;
    
    Graph G(n);
    for (int i = 0, u, v; i < n - 1; i++) {
        cin >> u >> v;
        u--, v--;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    
    LCA tree(G);
    queue<int> que;
    vector<int> dis(n, INF);
    for (int i = 0, x; i < k; i++) {
        cin >> x;
        x--;
        que.push(x);
        dis[x] = 0;
    }
    
    while (que.size()) {
        int u = que.front();
        que.pop();
        
        for (auto v: G[u])
            if (dis[v] == INF) {
                dis[v] = dis[u] + 1;
                que.push(v);
            }
    }
    
    auto dist = [&](int x, int y) {
        return min(tree.dist(x, y), dis[x] + dis[y]);
    };
    
    for (int i = 0, u, v; i < q; i++) {
        cin >> u >> v;
        u--, v--;
        cout << dist(u, v) << endl;
    }
    return 0;
}
  • 21
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值