题面
思路
这道题SPFA
会被卡,因此我们得考虑其他办法;
道路的特点:双向边,可能有环,边权非负
航线的特点:单向边,无环,边权可正可负
由此,我们认定只有道路的连通块是一个团;
也就是说在这个团内部,由于边权都是非负的,因此我们可以跑迪杰斯特拉;
而团与团之间是航线(有向边)连接的,如果我们将团看成一个整体,那么这就是一个DAG;
如图所示;
由于是DAG,或者说是拓扑图,我们按照拓扑序的方式去更新一个个团,就可以得到最短路了;
采用拓扑序线性更新最短路,是可以处理负权边的(因为每个点只能由入它的点更新,而一旦入度为 0 0 0,那么最短路径就确定了);
注意
一
在调用迪杰斯特拉函数时候,不需要重置判重数组;即memset(vis,0,sizeof vis)
;
假设 d i j k s t r a dijkstra dijkstra正在某一个团 t 1 t1 t1里面运行,那么 v i s vis vis数组只会涉及将团 t 1 t1 t1里面的那些点更新成 t r u e true true,等到在下一个团 t 2 t2 t2里面跑 d i j k s t r a dijkstra dijkstra的时候, v i s vis vis需要用到的点是团 t 2 t2 t2里面的点,由于这些点只属于团 t 2 t2 t2,不属于之前的团,所以这些点一定没有被之前的团里面的 d i j k s t r a dijkstra dijkstra给更新成 t r u e true true,所以我们不需要重置判重数组;
二
调用 d i j k s t r a dijkstra dijkstra算法的时候,要将连通块内的所有点都压入优先队列内;
这里和我们往常确认起点的迪杰斯特拉不同;
因为确认起点则默认距离最小且第一次访问
对于一个团来说,我们现在无法确定哪些点最小,即不知道起点是谁,因此我们将团内所有点放入优先队列;
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 25000 + 10,M = 100010;
struct Edge{
int next,to,val;
}e[M<<1];
vector<int> block[N];//每个连通块内存放的点
//每个点属于的块号
int dist[N],id[N],block_cnt,head[N];
bool vis[N];
int n,road,sail,start,tot;
int in[N];//连通块的入度
void add(int u,int v,int w){
e[++tot].to = v;
e[tot].val = w;
e[tot].next = head[u];
head[u] = tot;
}
void dfs(int u,int blockId){
id[u] = blockId;
block[blockId].push_back(u);
for(int i=head[u];i;i=e[i].next){
int to = e[i].to;
if(!id[to]) dfs(to,blockId);
}
}
queue<int> topq;
void dij(int blockId){
priority_queue<pii,vector<pii>,greater<pii>> pq;
for(auto ver : block[blockId]){
pq.push({dist[ver],ver});
}
while(!pq.empty()){
int u = pq.top().second;
pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int i=head[u];i;i=e[i].next){
int to = e[i].to;
if(dist[to] > dist[u] + e[i].val){
dist[to] = dist[u] + e[i].val;
//要求在同一连通块的内部才能入队迪杰斯特拉
if(id[to] == id[u]) pq.push({dist[to],to});
}
//如果这条边是连通块之间的 那么需要减少入度
if(id[to] != id[u]){
//如果入度为0 那么需要入队
if(--in[id[to]] == 0)
topq.push(id[to]);
}
}
}
}
void topsort(){
for(int i=1;i<=block_cnt;++i)
if(!in[i]) topq.push(i);
//连通块内迪杰斯特拉 连通块间线性更新
while(!topq.empty()){
int u = topq.front();
topq.pop();
dij(u);
}
}
const int INF = 0x3f3f3f3f;
void solve(){
cin >> n >> road >> sail >> start;
memset(dist,0x3f,sizeof dist);
dist[start] = 0;
for(int i=1,u,v,w;i<=road;++i){
cin >> u >> v >> w;
add(u,v,w);
add(v,u,w);
}
//DFS Flood Fill 扩展连通块
for(int i=1;i<=n;++i)
if(id[i] == 0)
dfs(i,++block_cnt);
for(int i=1,u,v,w;i<=sail;++i){
cin >> u >> v >> w;
add(u,v,w);
++in[id[v]];
}
topsort();
for(int i=1;i<=n;++i){
//因为负边权的存在 因此正无穷可能不是INF
if(dist[i] > INF/2) cout << "NO PATH\n";
else cout << dist[i] << '\n';
}
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}