#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int N = 25010,M = 150010,INF = 0x3f3f3f3f;
#define x first
#define y second
typedef pair<int,int> PII;
queue<int> q;
int e[M],w[M],h[N],ne[M],idx;
int din[N],bcnt,dist[N],id[N],st[N];
vector<int> block[N];
int n,mr,mp,s;
void add(int a,int b,int c)
{
e[idx] = b;ne[idx] = h[a],w[idx] = c,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 dijkstra(int bid)
{
priority_queue<PII,vector<PII>,greater<PII>> heap;
for(int x : block[bid]) heap.push({dist[x],x});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;
if(st[ver])continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if(id[j] == bid) heap.push({dist[j],j});
}
if(id[j] != bid && --din[id[j]] == 0) q.push(id[j]);
}
}
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
for(int i = 1; i <= bcnt; i ++ )
{
if(din[i] == 0) q.push(i);
}
while(q.size())
{
int t = q.front();q.pop();
dijkstra(t);
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> mr >> mp >> s;
while( mr -- )
{
int a,b,c;cin >> 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;cin >> a >> b >> c;
add(a, b, c);
din[id[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;
}
这是一段用于解决带有两类边的加权有向图单源最短路径问题的代码。第一类边是无向边,由mr条构成,第二类边是有向边,由mp条构成。
该算法主要有两个步骤,首先是将原图分成若干个“块”,每个块中的点可以通过第一类边直接到达。然后对每个块运行Dijkstra算法,每次将块内的点中距离起点最近的点取出,更新其相邻点的距离。注意到相邻点可能在不同的块中,因此对于每个块,需要维护一个堆,堆中存放该块中未确定最短路径的点。
接着,使用拓扑排序对所有的块进行遍历,每次从入度为0的块开始,将该块中所有点的最短路径求出来,更新相邻块的入度,若入度为0,则将其加入拓扑排序的队列中,等待遍历。最终得到每个点到起点的最短路径。
具体实现中,可以使用邻接表存储图,使用vector存储每个块中的点,使用数组dist记录起点到每个点的最短路径长度,使用数组id记录每个点属于哪个块,使用数组din记录每个块的入度,使用数组st记录每个点是否已经加入堆中。
关于代码中的一些细节,首先在dfs函数中,如果当前节点j的id为0,说明j还没有被加入任何一个块中,因此需要将其加入当前块中,并对j进行dfs。这里需要注意的是,dfs函数中的bid表示当前块的编号,不是目标块的编号。
其次,Dijkstra算法中的堆采用的是STL中的优先队列,其中存放的元素是PII类型,表示当前点的最短路径长度以及该点的编号。注意到在每次更新相邻点的距离时,只需要将距离小于之前的值的点加入堆中,因为在堆中的点已经满足其最短路径的值是当前已知的最小值,因此不需要再次更新。
最后,在topsort函数中,需要对每个块运行一次Dijkstra算法,因此需要遍历所有的块,并对入度为0的块进行拓扑排序。在拓扑排序中,对每个块运行一次Dijkstra算法,更新所有在该块中的点的最短路径,并对相邻块的入度进行更新。如果某个相邻块的入度为0,则将其加入拓扑排序的队列中,等待遍历。
注意:不是一个块中的相邻元素也可以被更新距离,只是不入堆跑dijkstra,而是跑topsort