Codeforces 545E 题解

题面

给定一张带正权的无向图和一个源点,求边权和最小的最短路径树。

$ n,m \leq 3 \times 10^5 $

感谢 @高飞@Luogu 提供的翻译

题解

什么是最短路径树 (其实CF英文题面中有):

考虑一个连通无向图 $G$,一个以顶点 $rt$ 为根节点的最短路径树 $T$ 是图 $G$ 满足下列条件的生成树——树 $T$ 中从根节点 $rt$ 到其它顶点 $u$ 的路径距离,在图 $G$ 中是从 $rt$ 到 $u$ 的最短路径距离。

所以,我们可以先求出这个原图中,从源点出发到所有点距离最短是多少,在求的过程中,便可以记录每个被扩展的点的父亲,以建出一个最短路径树。

联系最小生成树的思路,我们可以发现每次扩展点的时候,被扩展到的点的父边只要最小,就能保证最终得到的最短路径树最小。

实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge{
    int to,nxt,w;
}ed[300030*2];int edcnt=1;
int hd[300030];
inline void adde(int u,int v,int w){
    ed[++edcnt].to=v;
    ed[edcnt].nxt=hd[u];
    ed[edcnt].w=w;
    hd[u]=edcnt;
}
int n,m;
int vis[300030],p[300030];
ll dis[300030];
priority_queue<pair<ll,int> > pq;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,u,v,w;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        adde(u,v,w);adde(v,u,w);
    }
    int U;
    scanf("%d",&U);
    pq.push(make_pair(0ll,U));
    memset(dis,0x3f,sizeof dis);
    dis[U]=0;
    while(pq.size()){
        int u=pq.top().second;pq.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int id=hd[u];id;id=ed[id].nxt){
            int w=ed[id].w,v=ed[id].to;
            if(dis[u]+w<dis[v]){
                dis[v]=dis[u]+w;
                p[v]=id; // 记录扩展的点的父边
                pq.push(make_pair(-dis[v],v));
            }else if(dis[u]+w==dis[v]&&w<ed[p[v]].w)
                p[v]=id; // 如果有 满足到v的距离最短情况下 更短的父边 就修改一下
        }
    }
    ll ans=0;
    for(int i=1;i<=n;++i){
        if(i==U)continue;
        ans+=ed[p[i]].w;
    }
    printf("%lld\n",ans);
    for(int i=1;i<=n;++i){
        if(i==U)continue;
        printf("%d ",(p[i]>>1));
    }
}