题目大意:求源点s到各个点的单源最短路径
由于图中存在负权边,不能用dij;结合数据范围,spfa可能会被卡O(km),可以用SLF、LLL优化,但不一定能过。
解法一:正解(可能是正解):
因为道路之间是双向路径、航线是单向路径且无环(保证如果有一条航线可以从 Ai到 Bi,那么保证不可能通过一些道路和航线从 Bi回到 Ai。)
首先我们根据题意,其实需要先构思清楚到底整个图的结构是怎么样的,我们可以看到,道路都是无向边,故道路里面的点肯定彼此可达的,是联通图。
而航线是单向边且可以有负权边,这道题有一个很重要的性质-> 一旦满足 A 可达 B :A -> B,一定不存在任何通路使得 B 可达 A : B -> A
综合可得——》 有道路的连通块一定彼此可达,且一旦这个连通块中的点 A 向别的点 B 联通一条航线,那么A和B势必不在一个连通块【即没有道路可达】。联向的点 B 所在的连通块【单点也可以看作一个连通块】势必连通块的点没有任何一个点产生一条到 A所在的连通块的航线【即没有 B的连通块到 A的连通块的航线】。
故立马可抽象出,各个连通块抽象成一个点,且这些抽象出来的点构成拓扑图。且每个连通块内部的点,没有负权边,可以使用Dijkstra,而连通块之间可以使用 bfs【拓扑排序】。
可以认为线路形成的情况:
-
是由双向道路相连的点构成了连通块,由单向路(航线)连接连通块与连通块之间。
-
把每一个连通块看成一个大点,即一个有向无环图的最短路,可结合拓扑序列进行求解。
在每一个连通块内,由于都是正权边,可用dijkstra求最短路, 在结合拓扑序对块进行处理
实现思路
-
读入所有的道路(双向),在处理每个点时,用id[i]记录每个点属于哪个联通块,同时记录这个连通块中有哪些点。
-
读入所有的航线,同时维护一下航线到达站的入度值,便于拓扑排序使用
-
进行拓扑排序,遍历每个连通块,将入度为0的点入放入队列中
-
对于队列中放入的连通块,对连通块中所有的点做dijkstra
-
dij的处理,将这个连通块中所有点的放进堆中,取出队头,弹出,遍历其所有出边,判断这个点是否属于与队头节点属于同一个连通块。如果不属于,则出边所在的点所处的连通块入度减1,当所在连通块入度为0时,入队;属于同一个连通块,则将其放入堆中,同时更新dist的值。
#include<bits/stdc++.h> #define x first #define y second using namespace std; //无向边 50000*2+有向边50000 const int N=25010,M=150010,INF=0x3f3f3f3f; typedef pair<int,int> PII; int n,mr,mp,s; int id[N],dist[N],din[N];//id记录当前这个点数与哪个连通块 vector<int> block[N];//记录这个连通块里面有哪些点 bool st[N];//判重数组 int bcnt=0;//连通块的个数 queue<int>q; int h[N],e[M],w[M],ne[M],idx; void add(int a,int b,int c) { e[idx]=b; w[idx]=c; ne[idx]=h[a]; h[a]=idx++; } void dfs(int u, int bid) { id[u] = bid, block[bid].push_back(u); for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!id[j]) dfs(j, bid); } } void dij(int bid) { //将当前所有连通块中的点都插入到堆中 priority_queue<PII,vector<PII>,greater<PII> >heap; //将这个连通块中所有的点放进堆中 for(auto u:block[bid]) heap.push({dist[u],u}); while(heap.size()) { auto t=heap.top(); heap.pop(); int ver=t.y,distance=t.x; if(st[ver]) continue; st[ver]=true; for(int i=h[ver];~i;i=ne[i]) { int j=e[i]; //如果不属于同一个连通块,就是不同连通块间的边 if(id[j]!=id[ver] && --din[id[j]]==0) { q.push(id[j]); } if(dist[j]>dist[ver]+w[i]) { dist[j]=dist[ver]+w[i]; if(id[j]==id[ver])//是同一个连通块就放进堆中 { heap.push({dist[j],j}); } } } } } void topsort() { //初始化 memset(dist,0x3f,sizeof dist); dist[s]=0; //遍历连通块 for(int i=1;i<=bcnt;i++) { if(!din[i]) q.push(i); } while(q.size()) { int t=q.front(); q.pop(); dij(t); } } int main() { scanf("%d%d%d%d",&n,&mr,&mp,&s); memset(h,-1,sizeof h); // while(mr--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } //暴搜一个连通块 for(int i=1;i<=n;i++) { if(!id[i]) { dfs(i,++bcnt); } } while(mp--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); din[id[b]]++;//b所在的连通块的入度++ } topsort(); for(int i=1;i<=n;i++) { //无穷大的边判断就是大于一个非常大的数即可 if(dist[i]>INF/2) puts("NO PATH"); else printf("%d\n",dist[i]); } return 0; }
解法二:SLF优化SPFA
-
SPFA 的主要操作是把变化的点入队。这些点差不多是随机入队的,如果改变入队和出队的顺序,是否能加快所有点的最短路径计算?下面给出两个小优化。
1) 入队的优化:SLF(Small Label First)
需要使用STL的双端队列 deque。队头出队后,需要把它的有变化的邻居入队。把入队的点u 与新队头v进行比较,如果 dis[u]<dis[v],将 u 插入队头,否则插入队尾。这个优化使队列弹出的队头都是路径较短的点,从而加快所有点的最短路径的计算。
2) 出队的优化:LLL(Large Label Last)
计算队列中所有点的距离的平均值 x,每次选一个小于 的点出队。具体操作是:如果队头 的 dis[u]>x,把 弹出然后放到队尾去,然后继续检查新的队头 ,直到找到一个 dis[z]<x 为止。这个优化也是先处理了更短的点。 -
SLF 和LLL可以一起使用。
#include <bits/stdc++.h> using namespace std; const int N=400000 +100; int head[N],ver[N],Next[N],edge[N],vis[N],dis[N],tot; void add_edge(int a,int b,int c) { edge[tot]=b; ver[tot]=c; Next[tot]=head[a]; head[a]=tot++; } void spfa(int s) { memset(dis,0x3f,sizeof(dis)); memset(vis,false,sizeof(vis)); deque<int> q; dis[s]=0; vis[s]=true; q.push_back(s); while(q.size()) { int now=q.front(); vis[now]=false; q.pop_front(); for(int i=head[now]; ~i; i=Next[i]) { int j=edge[i]; if (dis[j]>dis[now]+ver[i]) { dis[j]=dis[now]+ver[i]; if (!vis[j]) { vis[j]=true; if (q.size() && dis[j]<dis[q.front()]) q.push_front(j); else q.push_back(j); } } } } } int main() { int t,r,p,s,x,y,z; ios::sync_with_stdio(false); cin.tie(0); cin>>t>>r>>p>>s; memset(head,-1,sizeof(head)); for(int i=1; i<=r; i++) { cin>>x>>y>>z; add_edge(x,y,z); add_edge(y,x,z); } for(int i=1; i<=p; i++) { cin>>x>>y>>z; add_edge(x,y,z); } spfa(s); for(int i=1; i<=t; i++) { if (dis[i]==0x3f3f3f3f) cout<<"NO PATH"<<endl; else cout<<dis[i]<<endl; } return 0; }