洛谷 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\)分,不行。接着考虑,如果走传送门比不走传送门要快的话,那么两点到离各自最近的传送门的距离相加,一定小于两点之间的唯一简单路径的长度,所以就可以对走传送门和不走传送门取最小值,就是答案。
实现步骤
-
先用\(bfs\)求出每个点到离它最近的传送门的距离,记为\(c_i\)。
-
然后\(dfs\)预处理\(lca\)的倍增数组。
-
最后对输入的数求\(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\),就连题目描述也描述的很模板,总体不难,还算简单。

浙公网安备 33010602011771号