D69 最短路 拓扑+Dijkstra 算法 P3008 [USACO11JAN] Roads and Planes G

D69 最短路 拓扑+Dijkstra 算法 P3008 [USACO11JAN] Roads and Planes G_哔哩哔哩_bilibili

 

P3008 [USACO11JAN] Roads and Planes G - 洛谷

思路

这道题求从源点到各点的最短路。题中给出的图有无向边和单向边,其中单向边边权可能为,这就告诉我们不能直接用 Dijkstra,用 SPFA 可能被卡。

注意到无向边边权是非负的,这提示我们可以在无向边上跑 Dijkstra。如果将无向边连接的点缩为一点,这个图就是一个DAG,DAG 可以用拓扑求最短路。

所以我们就分开考虑,首先求出若干个由无向边组成的连通块,对于块内的点,通过 Dijkstra 更新最短路;然后在块与块之间,用拓扑排序一层一层地进行更新;最后就可以求出源点 s 到各点的最短路了。

          image

相关板子:

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

 

// 最短路 拓扑+Dijkstra 算法 O(mlogn)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define pii pair<int,int>
using namespace std;

const int N=25005;
int n,r,p,s;
vector<pii> e[N];
int bel[N],cnt,rd[N],vis[N],d[N];
vector<int> block[N];

void dfs(int u){
  bel[u]=cnt;
  block[cnt].push_back(u);
  for(auto [v,w]:e[u]) if(!bel[v]) dfs(v);
}
void work(){
  memset(d,0x7f,sizeof d);//0x7f>0x3f
  d[s]=0;
  queue<int> q;//队列
  priority_queue<pii,vector<pii>,greater<pii> > pq;//小根堆
  q.push(bel[s]);//s块入队
  for(int i=1;i<=cnt;i++) if(!rd[i]) q.push(i);//入度为0的块入队
  while(!q.empty()){ //块外拓扑
    int b=q.front();q.pop();
    for(int u:block[b]) pq.push({d[u],u});//块内点入堆
    while(!pq.empty()){ //块内Dijkstra
      int u=pq.top().second;pq.pop();
      if(vis[u]) continue;
      vis[u]=1;
      for(auto [v,w]:e[u]){
        if(d[v]>d[u]+w){
          d[v]=d[u]+w;
          if(bel[v]==bel[u]) pq.push({d[v],v});//点入堆
        }
        if(bel[v]!=bel[u]&&(--rd[bel[v]])==0) q.push(bel[v]);//块入队
      }
    }
  }  
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>r>>p>>s;//城镇的数量,道路的数量,航线的数量,中心城镇
  for(int i=1,a,b,c;i<=r;i++){ //双向边
    cin>>a>>b>>c;
    e[a].push_back({b,c});
    e[b].push_back({a,c});
  }
  for(int i=1;i<=n;i++)if(!bel[i]) ++cnt,dfs(i);//缩点
    
  for(int i=1,a,b,c;i<=p;i++){ //单向边
    cin>>a>>b>>c;
    e[a].push_back({b,c});
    rd[bel[b]]++;//块的入度
  }
  
  work();//拓扑+Dijkstra
  
  for(int i=1;i<=n;i++)
    if(d[i]>inf) cout<<"NO PATH"<<'\n';
    else cout<<d[i]<<'\n';
}

 

// 双端队列优化SPFA算法
// 距离小于队头则从队头入队,否则从队尾入队
#include<bits/stdc++.h>
using namespace std;

const int N=25010;
int n,r,p,s,a,b,c;
vector<pair<int,int>> e[N];
int d[N],inq[N];

void spfa(int s){
  memset(d,0x3f,sizeof d); d[s]=0;
  deque<int> q; q.push_back(s); inq[s]=1;
  while(q.size()){
    int u=q.front(); q.pop_front(); inq[u]=0;
    for(auto [v,w]:e[u]){
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        if(!inq[v]){
          if(q.size()&&d[v]<d[q.front()]) q.push_front(v);
          else q.push_back(v);
          inq[v]=1;
        }
      }
    }
  }
}
int main(){
  ios::sync_with_stdio(0);
  cin>>n>>r>>p>>s;
  for(int i=1; i<=r; i++){
    cin>>a>>b>>c;
    e[a].push_back({b,c});
    e[b].push_back({a,c});
  }
  for(int i=1; i<=p; i++){
    cin>>a>>b>>c;
    e[a].push_back({b,c});
  }
  
  spfa(s);
  
  for(int i=1; i<=n; i++)
    if(d[i]==0x3f3f3f3f) cout<<"NO PATH"<<"\n";
    else cout<<d[i]<<"\n";
}

 

posted @ 2026-02-14 08:35  董晓  阅读(66)  评论(0)    收藏  举报