D92【模板】最短路径树 Dijkstra 算法 CF545E Paths and Trees

D92【模板】最短路径树 Dijkstra 算法 CF545E Paths and Trees_哔哩哔哩_bilibili

 

CF545E Paths and Trees - 洛谷

最短路径树ShortestPathTree, SPT)是连通图的一个生成树,从根到任意点的路径都为原图中根到任意点的最短路径

最小生成树(Minimum Spanning Tree, MST)是连通图的一个生成树,包含所有的顶点且边权之和最小

最短路径树(左图)的边权和 $\ge$ 最小生成树(右图)的边权和

image

以某点为根的最短路径树可能有多颗,合起来就构成最短路径图,最短路径图可以看做以根为起点的 DAG 图

如何求最短路径树?

最短路径树是从根节点到任一点的路径都为最短路,可以用单源最短路径算法 Dijkstra

从根节点开始扩展,每次松弛操作,更新最短路 d 数组,同时使用 pre 数组来记录点 v 的前驱边

要保证生成树上所有的边权和最小,还要考虑一种情况,如果松弛前的结果与松弛后的结果相等(d[v]==d[u]+w),并且当前这条前驱边比以前的前驱边更小(w<w[pre[v]] ),那么更新当前前驱边的 pre[v]=i

图中节点 3 先被前驱边 3 更新,后被前驱边 2 更新。后一次被更新一定更小,所以松弛条件也可以改为:d[v]>=d[u]+w

image

image   image

因为用前向星存边,双向边的编号是挨着的,所以输出边的编号 $\frac{pre[i]+1}{2}$

相关板子:

D02【模板】最短路 Dijkstra 算法 P4779 单源最短路径 - 董晓 - 博客园

 

// 最短路树 Dijkstra 算法 O(MlogN)
#include<bits/stdc++.h>
#define ll long long
#define pli pair<ll,int>
using namespace std;

const int N=3e5+5;
int h[N],to[N<<1],ne[N<<1],idx; ll ww[N<<1];
void add(int u,int v,ll w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
int n,m,s;
int pre[N]; bool vis[N];
ll d[N];

void dijkstra(int s){
  memset(d,0x3f,sizeof(d)); d[s]=0;
  priority_queue <pli,vector<pli>,greater<pli> > q;
  q.push({0,s});
  while(!q.empty()){
    int u=q.top().second;q.pop();
    if(vis[u])continue; vis[u]=1;
    for(int i=h[u];i;i=ne[i]){
      int v=to[i],w=ww[i];
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        pre[v]=i; //保存前驱边
        q.push({d[v],v});
      }
      if(d[v]==d[u]+w && w<ww[pre[i]]) pre[v]=i; //保存前驱边
    }
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1,x,y,z;i<=m;i++){
    scanf("%d%d%d",&x,&y,&z);
    add(x,y,z);add(y,x,z);
  }
  scanf("%d",&s);
  
  dijkstra(s);
  ll sum=0;
  for(int i=1;i<=n;i++)if(i!=s)sum+=ww[pre[i]]; //计算最短路树的边权和
  printf("%lld\n",sum);
  for(int i=1;i<=n;i++)if(i!=s)printf("%d ",(pre[i]+1)/2);
}

 

// 最短路树 Dijkstra 算法 O(MlogN)
#include<bits/stdc++.h>
#define ll long long
#define pli pair<ll,int>
using namespace std;

const int N=3e5+5;
int h[N],to[N<<1],ne[N<<1],idx; ll ww[N<<1];
void add(int u,int v,ll w){
  to[++idx]=v;ww[idx]=w;ne[idx]=h[u];h[u]=idx;
}
int n,m,s;
int pre[N]; bool vis[N];
ll d[N];

void dijkstra(int s){
  memset(d,0x3f,sizeof(d)); d[s]=0;
  priority_queue <pli,vector<pli>,greater<pli> > q;
  q.push({0,s});
  while(!q.empty()){
    int u=q.top().second;q.pop();
    if(vis[u])continue; vis[u]=1;
    for(int i=h[u];i;i=ne[i]){
      int v=to[i],w=ww[i];
      if(d[v]>=d[u]+w){
        d[v]=d[u]+w;
        pre[v]=i; //保存前驱边
        q.push({d[v],v});
      }
    }
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1,x,y,z;i<=m;i++){
    scanf("%d%d%d",&x,&y,&z);
    add(x,y,z);add(y,x,z);
  }
  scanf("%d",&s);
  
  dijkstra(s);
  ll sum=0;
  for(int i=1;i<=n;i++)if(i!=s)sum+=ww[pre[i]]; //计算最短路树的边权和
  printf("%lld\n",sum);
  for(int i=1;i<=n;i++)if(i!=s)printf("%d ",(pre[i]+1)/2);
}

 

posted @ 2026-02-26 22:39  董晓  阅读(73)  评论(0)    收藏  举报