题链:https://nanti.jisuanke.com/t/42388
题意:一个有向图,有双向边(边权全为正),单向边(边权可能为负),求一个起点到其他点的最短路。
思路:有负权边且卡spfa。注意题目中有一个非常重要的条件。
也就是环中不可能有负权边。那么,我们考虑tarjan缩点,那么在一个强连通分量中的点之间的边都是正权边,那么内部的最短路就能用Dijkstra了。
缩点后,图变成一个树,强连通分量之间的最短路就能用Topo排序求了。
注意,分块是指一个强连通分量(一个缩点),一个强连通分量的处理。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e4+10;
const int M = 2e5+10;
const int inf = 0x3f3f3f3f;
int n,x,y,s;
struct node{
int to,w,nex;
}g[M];
struct Node{
int to,d;
friend bool operator<(Node a,Node b){
return a.d>b.d;
}
};
int cnt,head[N];
int dis[N],in[N];
void add(int u,int v,int w){
g[cnt]=node{v,w,head[u]};
head[u]=cnt++;
}
int dfn[N],top,id,low[N],sta[N],c[N],cl,vis[N];
void init(){
cl=top=id=cnt=0;
for(int i=1;i<=n;i++)
dfn[i]=in[i]=0,dis[i]=inf,head[i]=-1;
}
vector<int> scc[N];//每个强连通分量中有那些点
//tarjan求强连通分量
void tarjan(int u){
low[u]=dfn[u]=++id; sta[++top]=u; vis[u]=1;
for(int i=head[u];~i;i=g[i].nex){
int v=g[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
c[u]=++cl;
scc[cl].push_back(u);
while(sta[top]!=u){
c[sta[top]]=cl;
scc[cl].push_back(sta[top]);
vis[sta[top--]]=0;
}
vis[sta[top--]]=0;
}
}
vector<int> ng[N];//缩点后的图,一棵树
unordered_map<int,unordered_map<int,int> > mp; //标记重边
vector<int> ok[N];//强连通分量可以作起点(与另一强连通分量中的点相连的点,有可能更新最短路)的点
//建新图,用来Topo和分块处理
void getng(){
tarjan(s);
for(int u=1;u<=n;u++){
if(!c[u]) continue;
for(int i=head[u];~i;i=g[i].nex){
int v=g[i].to;
if(!c[v]||c[u]==c[v]) continue;
ok[c[v]].push_back(v);
if(mp[c[u]][c[v]]) continue;
in[c[v]]++;
mp[c[u]][c[v]]=1;
ng[c[u]].push_back(c[v]);
}
}
}
bool book[N];
//Dijkstra求强连通分量内部的最短路
void dijs(int st){
priority_queue<Node> pq;
for(int i=1;i<=n;i++)
book[i]=0;
pq.push(Node{st,dis[st]});
while(!pq.empty()){
Node now = pq.top();
pq.pop();
int u=now.to;
if(book[u]) continue;
book[u]=1;
for(int i=head[u];~i;i=g[i].nex){
int v=g[i].to;
if(c[u]!=c[v]) continue;
if(dis[v]>dis[u]+g[i].w){
dis[v]=dis[u]+g[i].w;
pq.push(Node{v,dis[v]});
}
}
}
}
//Topo排序求强连通分量之间的最短路
queue<int> q;
void topo(int st){
int len = scc[st].size();
for(int j=0;j<len;j++){//此强连通分量中的所有点
int u=scc[st][j];
for(int i=head[u];~i;i=g[i].nex){//更新其他强连通分量中的点
int v=g[i].to;
if(c[u]==c[v]) continue;
dis[v]=min(dis[v],dis[u]+g[i].w);
}
}
len=ng[st].size();
for(int i=0;i<len;i++){//Topo排序
int v=ng[st][i];
in[v]--;
if(in[v]==0){
int siz=ok[v].size();
for(int j=0;j<siz;j++)
q.push(ok[v][j]);
}
}
}
int main(void){
scanf("%d%d%d%d",&n,&x,&y,&s);
init();
for(int i=1;i<=x;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
for(int i=1;i<=y;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
getng();
dis[s]=0;
q.push(s);
while(!q.empty()){
int u =q.front();
q.pop();
dijs(u);
if(q.empty()||c[q.front()]!=c[u])//此强连通分量都已处理完
topo(c[u]);
}
for(int i=1;i<=n;i++)
if(dis[i]==inf)
puts("NO PATH");
else
printf("%d\n",dis[i]);
return 0;
}