最小距离「多源最短路」
最小距离「多源最短路」
题目描述
给定一张 \(n\) 个点 \(m\) 条边的带边权连通无向图,其中有 \(p\) 个点是特殊点。
对于每个特殊点,求出它到离它最近的其它特殊点的距离。
输入格式
第一行三个整数 \(n,m,p\),第二行 \(p\) 个整数 \(x_1~x_p\) 表示特殊点的编号。接下来 \(m\) 行每行三个整数 \(u,v,w\) 表示一条连接 \(u\) 和 \(v\),长度为 \(w\) 的边。
输出格式
输出一行 \(p\) 个整数,第 \(i\) 个整数表示 \(x_i\) 的答案。
样例
样例输入
5 6 3
2 4 5
1 2 4
1 3 1
1 4 1
1 5 4
2 3 1
3 4 3
样例输出
3 3 5
数据范围与提示
对于 \(10\%\) 的数据,\(n,m<=50000,p<=10\) 。
对于 \(40\%\) 的数据,\(n,m<=50000\)。
对于另外 \(5\%\) 的数据,\(p=n\)。
对于 \(100\%\) 的数据,\(1<=n,m<=2e5,2<=p<=n,1<=x_i<=n\),\(x_i\) 互不相同,\(1<=u,v<=n,1<=w<=1e9\)。
思路分析
又是一个奇奇怪怪的最短路
- 这题显然暴力的话是以所有特殊点为起点跑一遍最短路,然而只能过俩点
- 所以我们考虑只跑一遍最短路,这时候就引入了一个概念——多源最短路(不是严格意义上的,因为无法求出任意两点的最短距离)
- 广为人知的 \(Floyd\) 就是典型的多源最短路,那其他求最短路的算法就不能算了吗?
- 然而事实是 \(Floyd\) 的 \(O(n^3)\) 的效率使单源最短路算法被迫转型,其实只要稍微改动一部分就行了
- 以 \(Dijkstra\) 为例( \(Spfa\) 也可以,但做法有差异),将所有起点都压入堆里,距离置为 \(0\),这样我们就可以求出每个点到它最近的起点的距离
- 跑完 \(Dijkstra\) 后,我们枚举每一条边,如果边两端的点最近的起点不同,即是由不同的起点拓展的,那么我们就可以对相应的起点进行更新
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define N 400010
#define R register
#define int long long
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,p,spl[N],head[N],f[N];
long long ans[N],dis[N];
bool vis[N],flag[N];
struct edge{
int to,next,dis;
}e[N<<1];
int len = 1;
void addedge(int u,int v,int w){
e[++len].to = v;
e[len].dis = w;
e[len].next = head[u];
head[u] = len;
}
struct node{
int num,dis;
node(){}
node(int _num,int _dis){num = _num,dis = _dis;}
bool operator <(const node &a)const{
return dis > a.dis;
}
};
void Dij(){
priority_queue<node>q;
fill(dis+1,dis+1+n,1e18);
for(int i = 1;i <= p;i++){
dis[spl[i]] = 0,f[spl[i]] = spl[i]; //不同起点的起点是本身
q.push(node(spl[i],0));
}
while(!q.empty()){
node p = q.top();q.pop();
int u = p.num;
if(vis[u])continue;
vis[u] = 1;
for(R int i = head[u];i;i = e[i].next){
int v = e[i].to;
if(dis[v]>dis[u]+e[i].dis){
dis[v] = dis[u]+e[i].dis;
f[v] = f[u]; //记录起点
q.push(node(v,dis[v]));
}
}
}
}
signed main(){
n = read(),m = read(),p = read();
for(R int i = 1;i <= p;i++){
int x;x = read();
spl[i] = x,flag[x] = 1;
}
for(R int i = 1;i <= m;i++){
int u,v,w;u = read(),v = read(),w = read();
addedge(u,v,w),addedge(v,u,w);
}
Dij();
fill(ans+1,ans+1+n,1e18);
for(int i = 2;i <= len;i += 2){
int x = e[i].to,y = e[i^1].to,w = e[i].dis; //反向边的终点即原边的起点,这样就可以确定每条边的端点了
if(f[x]!=f[y]){
ans[f[x]] = min(ans[f[x]],dis[x]+dis[y]+w);
ans[f[y]] = min(ans[f[y]],dis[x]+dis[y]+w);
}
}
for(int i = 1;i <= p;i++)printf("%lld ",ans[spl[i]]);
return 0;
}