联赛模拟测试23 C. 最小距离 多源最短路
题目描述
分析
以所有特殊点为起点跑多源最短路,并且记录每个点是由哪个源点拓展的。
然后枚举所有边,如果边的两端是由不同源点拓展的,就更新这两个点的答案。
不难证明,对于源点 \(i\),由 \(i\) 拓展的点 \(j\) 以及与 \(j\) 相邻且不由 \(i\) 拓展的点 \(k\),
如果 \(i\) 的最优路径从 \(j\) 走到了 \(k\),那么走到拓展 \(k\) 的源点是最优的。因此这个做
法是正确的。
时间复杂度 \(O((n+m)logn)\)
代码
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
typedef long long ll;
const int maxn=1e6+5;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int head[maxn],tot=1;
struct asd{
int from,to,next,val;
}b[maxn];
void ad(int aa,int bb,int cc){
b[tot].from=aa;
b[tot].to=bb;
b[tot].val=cc;
b[tot].next=head[aa];
head[aa]=tot++;
}
int n,m,p,x[maxn],f[maxn];
ll dis[maxn],ans[maxn];
bool vis[maxn];
struct jie{
int num;
ll jl;
jie(int aa,ll bb){
num=aa,jl=bb;
}
bool operator < (const jie& A)const{
return jl>A.jl;
}
};
void dij(){
std::priority_queue<jie> q;
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=p;i++){
dis[x[i]]=0;
f[x[i]]=x[i];
q.push(jie(x[i],0));
}
while(!q.empty()){
int now=q.top().num;
q.pop();
if(vis[now]) continue;
vis[now]=1;
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(dis[u]>dis[now]+(ll)b[i].val){
dis[u]=dis[now]+(ll)b[i].val;
f[u]=f[now];
q.push(jie(u,dis[u]));
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
n=read(),m=read(),p=read();
for(int i=1;i<=p;i++){
x[i]=read();
}
for(int i=1;i<=m;i++){
int aa,bb,cc;
aa=read(),bb=read(),cc=read();
ad(aa,bb,cc);
ad(bb,aa,cc);
}
dij();
memset(ans,0x3f,sizeof(ans));
for(int i=1;i<tot;i+=2){
int aa=b[i].from,bb=b[i].to,cc=b[i].val;
if(f[aa]!=f[bb]){
ans[f[aa]]=std::min(ans[f[aa]],dis[aa]+dis[bb]+cc);
ans[f[bb]]=std::min(ans[f[bb]],dis[aa]+dis[bb]+cc);
}
}
for(int i=1;i<=p;i++){
printf("%lld ",ans[x[i]]);
}
printf("\n");
return 0;
}