D98 最短路径树+并查集 Dijkstra 算法 P2934 [USACO09JAN] Safe Travel G

D98 最短路径树+并查集 Dijkstra 算法 P2934 [USACO09JAN] Safe Travel G_哔哩哔哩_bilibili

 

P2934 [USACO09JAN] Safe Travel G - 洛谷

给一个无向图,求在不经过原来 1 号点到 i 号点最短路上最后一条边的前提下,1 号点到 i 号点的最短路。

思路

题目说保证从 1 号点到任意点的最短路径唯一,也就是说建立以 1 为根的最短路径树只有一颗

不经过最后一条边 (fa,i) 的新的最短路一定会经过一条非树边 (u,v),其中 u 在 i 子树中,而 v 不在 i 的子树中

观察可得,新的最短路 $ans[i]=min(d_u+w+d_v)-d_i$。怎样找到最优的非树边?

我们将所有非树边 (u,v) 按 $d_u​+w+d_v$​ 从小到大排序,再枚举非树边能更新的点,每个点第一次被更新时就是答案

我们怎样知道非树边 (u,v) 能更新哪些点?观察发现,非树边只能更新 (u,v) 到 lca 之间的点

不用求 lca,我们用并查集,自底向上,一边压缩路径,一边更新答案

图中,u,v 两个点交替向上爬,爬过谁更新谁,都爬到 lca 就结束,再换下一条边爬

 

相关板子:

D92【模板】最短路径树 Dijkstra 算法 CF545E Paths and Trees - 董晓 - 博客园

C01【模板】并查集 - 董晓 - 博客园

 

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

const int N=100005;
vector<pli> e[N];
int n,m;
int pa[N],dep[N];
ll d[N];

void dijkstra(){
  memset(d,0x3f,sizeof d); d[1]=0;
  priority_queue<pli,vector<pli>,greater<pli> > q;
  q.push({0,1});
  while(!q.empty()){
    auto [dd,u]=q.top(); q.pop();
    if(dd!=d[u])continue;
    for(auto [w,v]:e[u]){
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        pa[v]=u;         //记录父节点
        dep[v]=dep[u]+1; //记录深度
        q.push({d[v],v});
      }
    }
  }
}

struct node{int u,v; ll d;}nt[N<<1];
int cnt,fa[N];
ll ans[N];

int find(int u){
  return (u==fa[u])?u:(fa[u]=find(fa[u]));
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++){
    int a,b; ll t;
    scanf("%d%d%lld",&a,&b,&t);
    e[a].push_back({t,b});
    e[b].push_back({t,a});
  }
  
  dijkstra();
  for(int u=1;u<=n;u++){
    ans[u]=-1; fa[u]=u; //并查集初值
    for(auto [w,v]:e[u])if(pa[v]!=u&&pa[u]!=v&&u<v)
      nt[++cnt]={u,v,d[u]+d[v]+w}; //记录非树边
  }
  sort(nt+1,nt+cnt+1,[](node a,node b){return a.d<b.d;});
  for(int i=1;i<=cnt;i++){ //枚举非树边
    int u=nt[i].u, v=nt[i].v; ll dd=nt[i].d; //取出端点和距离
    u=find(u),v=find(v); //找出端点的根
    while(u!=v){ //都爬到lca结束
      if(dep[u]<dep[v])swap(u,v); //保证u点深
      ans[u]=dd-d[u]; //更新u点答案
      fa[u]=pa[u]; //u指向它的父亲
      u=find(u);   //找出u点的根
    }
  }
  for(int i=2;i<=n;i++)printf("%lld\n",ans[i]);
}

 

posted @ 2026-03-05 20:39  董晓  阅读(21)  评论(0)    收藏  举报