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 - 董晓 - 博客园
// 最短路径树+并查集 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]); }
浙公网安备 33010602011771号