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

题目背景

对应的选择、判断题:https://ti.luogu.com.cn/problemset/1108

题目描述

小杨准备前往 B 国旅游。

B 国有 \(n\) 座城市,这 \(n\) 座城市依次以 \(1\) 至 \(n\) 编号。城市之间由 \(n-1\) 条双向道路连接,任意两座城市之间均可达(即任意两座城市之间存在可达的路径)。

小杨可以通过双向道路在城市之间移动,通过一条双向道路需要 \(1\) 单位时间。

B 国城市中有 \(k\) 座城市设有传送门。设有传送门的城市的编号依次为 \(b_1,b_2, \cdots ,b_k\)。小杨可以从任意一座设有传送门的城市花费 \(0\) 单位时间前往另一座设有传送门的城市。

注:如果两座设有传送门的城市之间存在双向道路,那么小杨可以选择通过双向道路移动,也可以选择通过传送门传送。

小杨计划在 B 国旅游 \(q\) 次。第 \(i\) 次旅游(\(1 \le i \le q\)),小杨计划从编号为 \(u_i\) 的城市前往编号为 \(v_i\) 的城市,小杨希望你能求出所需要的最短时间。

输入格式

第一行包含三个正整数 \(n,k,q\),分别表示 B 国的城市数量,设有传送门的城市数量,以及小杨计划在 B 国旅游的次数。
接下来 \(n-1\) 行,每行包含两个正整数 \(x_i, y_i\),表示一条双向道路连接的两座城市的编号。
第 \(n + 1\) 行包含 \(k\) 个正整数,表示设有传送门的城市的编号。
接下来 \(q\) 行,每行包含两个正整数 \(u_i,v_i\),表示小杨第 \(i\) 次旅游行程的起点城市编号与终点城市编号。

输出格式

输出共 \(q\) 行。第 \(i\) 行(\(1 \leq i \leq q\))输出一个非负整数,表示小杨计划第 \(i\) 次旅游从编号为 \(u_i\) 的城市前往编号为 \(v_i\) 的城市所需要的最短时间。

输入输出样例 #1

输入 #1

7 2 1
5 7
3 6
2 3
1 5
5 4
1 2
7 4
3 7

输出 #1

4

输入输出样例 #2

输入 #2

5 0 3
2 3
5 1
5 2
1 4
4 5
1 4
4 3

输出 #2

2
1
4

说明/提示

子任务 分值 $n \leq $ $ k \leq $ $q \leq $
\(1\) \(30\) \(500\) \(500\) \(1\)
\(2\) \(30\) \(2 \times 10^5\) \(0\) \(2 \times 10^5\)
\(3\) \(40\) \(2 \times 10^5\) \(2 \times 10^5\) \(2 \times 10^5\)

对全部的测试数据,\(1 \leq n \leq 2 \times 10^5\),\(0 \leq k \leq n\),\(1 \leq x_i, y_i, u_i, v_i \leq n\),\(u_i \neq v_i\)。

算法分析

首先如果没有传送门,那这题很简单,跑一边\(dijkstra\)堆优化就能过,是模板。加上传送门也可以建边,在有传送门的每一座城市依次连边权为\(0\)的边,但时间复杂度为\(O(k)\),而且\(dijkstra\)每一遍是\(O(nlogk)\),要跑\(q\)遍,时间复杂度为\(O(nqlogk)\),太大了,会\(TLE\),只能得\(30\)分,不行。接着考虑,如果走传送门比不走传送门要快的话,那么两点到离各自最近的传送门的距离相加,一定小于两点之间的唯一简单路径的长度,所以就可以对走传送门和不走传送门取最小值,就是答案。

实现步骤

  1. 先用\(bfs\)求出每个点到离它最近的传送门的距离,记为\(c_i\)。

  2. 然后\(dfs\)预处理\(lca\)的倍增数组。

  3. 最后对输入的数求\(lca\),和传送门求最小值,输出。

暴力代码

#include<bits/stdc++.h>
using namespace std;
struct node{
    long long val;
    int to;
    int next;
};
int n,k,m;
int cnt = 0;
int t[200005];
long long b[200005];
node c[1000005];
int head[200005];
bool vis[200005];
priority_queue<pair<long long,int>,vector<pair<long long,int>>,greater<pair<long long,int>>> pq;//堆
void dijkstra(int x){//堆优化
    for(int i = 0; i<=n; i++){
        b[i] = LONG_LONG_MAX;//初始化
        vis[i] = 0;
    }
    b[x] = 0;
    pq.push(make_pair(0,x));
    while(!pq.empty()){
        int pos = pq.top().second;
        pq.pop();
        if(vis[pos]==1){
            continue;
        }
        vis[pos] = 1;
        for(int i = head[pos]; i; i = c[i].next){
            if(b[pos]+c[i].val<b[c[i].to]){
                b[c[i].to] = b[pos]+c[i].val;
                pq.push(make_pair(b[c[i].to],c[i].to));
            }
        }
    }
}
int main(){
    cin>>n>>k>>m;
    for(int i = 1; i<n; i++){
        int x,y;
        cin>>x>>y;
        //双向边
        cnt++;
        c[cnt] = node{1,y,head[x]};
        head[x] = cnt;
        cnt++;
        c[cnt] = node{1,x,head[y]};
        head[y] = cnt;
    }
    for(int i = 1; i<=k; i++){
        cin>>t[i];
    }
    for(int i = 1; i<=k; i++){//暴力建边
        for(int j = i+1; j<=k; j++){
            //双向遍
            cnt++;
            c[cnt] = node{0,t[j],head[t[i]]};
            head[t[i]] = cnt;
            cnt++;
            c[cnt] = node{0,t[i],head[t[j]]};
            head[t[j]] = cnt;
        }
    }
    for(int i = 1; i<=m; i++){
        int x,y;
        cin>>x>>y;
        dijkstra(x);//跑dijkstra
        cout<<b[y]<<'\n';
    }
    return 0;
}

AC代码

两点间的最短路

#include<bits/stdc++.h>
using namespace std;
int n,k,m;
queue<int> q;
int c[200005];
int dep[200005];
bool vis[200005];
int fa[200005][25];
vector<int> a[200005];
void bfs(){//模板的bfs求两点间的最短路
    while(!q.empty()){
        int pos = q.front();
        q.pop();
        for(auto i : a[pos]){
            if(vis[i]==0){
                vis[i] = 1;
                c[i] = c[pos]+1;
                q.push(i);
            }
        }
    }
}
void dfs(int x,int f){//模板的dfs预处4
    dep[x] = dep[f]+1;
    fa[x][0] = f;
    for(int i = 1; i<=19; i++){
        fa[x][i] = fa[fa[x][i-1]][i-1];
    }
    for(auto i : a[x]){
        if(i!=f){
            dfs(i,x);
        }
    }
}
int lca(int x,int y){//倍增求lca,模板
    if(dep[x]<dep[y]){
        swap(x,y);
    }
    for(int i = 19; i>=0; i--){
        if(dep[fa[x][i]]>=dep[y]){
            x = fa[x][i];
        }
    }
    if(x==y){
        return y;
    }
    for(int i = 19; i>=0; i--){
        if(fa[x][i]!=fa[y][i]){
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}
int main(){
    cin>>n>>k>>m;
    for(int i = 1; i<n; i++){
        int x,y;
        cin>>x>>y;
        //建边
        a[x].push_back(y);
        a[y].push_back(x);
    }
    memset(c,0x3f3f3f3f,sizeof(c));//初始化为极大值
    for(int i = 1; i<=k; i++){
        //一次性把所有传送门全部押入队列
        int x;
        cin>>x;
        q.push(x);
        c[x] = 0;
        vis[x] = 1;
    }
    //预处理
    dep[1] = 1;
    dfs(1,0);
    bfs();
    while(m--){
        int x,y;
        cin>>x>>y;
        cout<<min(c[x]+c[y],dep[x]+dep[y]-dep[lca(x,y)]*2)<<'\n';//取最小值
    }
    return 0;
}

总结

这道题不难想思路,但涉及到的算法有很多,出题人想用算法海来难住我们,不过每一个算法都是模板,暴力也是模板的\(dijkstra\),就连题目描述也描述的很模板,总体不难,还算简单。

posted @ 2026-08-24 21:13  xmgene  阅读(12)  评论(0)    收藏  举报