农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。
他想把牛奶送到 T 个城镇,编号为 1∼T。
这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。
每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。
对于道路,0≤Ci≤10,000;然而航线的花费很神奇,花费 Ci 可能是负数(−10,000≤Ci≤10,000)。
道路是双向的,可以从 Ai 到 Bi,也可以从 Bi 到 Ai,花费都是 Ci。
然而航线与之不同,只可以从 Ai 到 Bi。
事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai。
由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。
他想找到从发送中心城镇 S 把奶牛送到每个城镇的最便宜的方案。
输入格式
第一行包含四个整数 T,R,P,S。
接下来 R 行,每行包含三个整数(表示一个道路 Ai,Bi,Ci。
接下来 P 行,每行包含三个整数(表示一条航线)Ai,Bi,Ci。
输出格式
第 1..T 行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出 NO PATH
。
数据范围
1≤T≤25000,
1≤R,P≤50000,
1≤Ai,Bi,S≤T
输入样例:
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
输出样例:
NO PATH
NO PATH
5
0
-95
-100
题意: 一个n点的有向图,其中有r条道路和p条航线,道路是双向的且边权非负,航向是单向的且权值可能为负数,求从起点s到各点的最短距离。
分析: 这道题目可以用slf优化后的spfa水过去的,见[spfa][slf优化]道路与航线 AcWing342,但那毕竟不是正解,算是投机取巧的方法了,所以这里记录一下正解。
首先注意到题目中有一句很重要的话:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai。这句话的信息量是相当大的,如果只把双向边加入图,那么就构成了若干个连通块,同时单向边会连接若干连通块,成为它们之间连接的桥梁,另外由于不存在单向边构成的环,如果把连通块缩成一个点,那么最终会形成一个有向无环图。
由于连通块内部边权都是正的,这些点的最短路可以跑dijkstra,连通块和连通块之间的距离则通过拓扑排序来求出。具体过程是对连通块进行拓扑排序,遍历当前连通块内部点,若存在点dis[i] != inf,则将其作为起点加入优先队列,然后在连通块内部跑dijkstra,之后再遍历内部点,如果存在点通过单向边连接到其它连通块,则用dis[i]+w更新一下dis[to],同时to所在连通块的入度减1,这里有一个细节,当dis[i]为inf时不可以更新,因为w可以为负数,dis[to]和dis[i]都是inf,此时更新会让dis[to]变小,这样最后统计无法到达的点时会很麻烦。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <queue>
#define pii pair<int, int>
#define to first
#define w second
#define inf 0x3f3f3f3f
using namespace std;
int n, m1, m2, s, fa[25005], in[25005], dis[25005];
bool vis[25005];
vector<pii> g1[25005], g2[25005];//g1道路图,g2航线图
vector<int> ele[25005];//ele记录连通块内各点
int find(int x)
{
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void dijkstra(int id)//在id连通块内部跑最短路
{
priority_queue<pii, vector<pii>, greater<pii> > a;
for(int i = 0; i < ele[id].size(); i++)//加入可能的起点
if(dis[ele[id][i]] != inf)
a.push(make_pair(dis[ele[id][i]], ele[id][i]));
while(a.size())
{
int now = a.top().second;
a.pop();
if(vis[now]) continue;
vis[now] = true;
for(int i = 0; i < g1[now].size(); i++)
{
int to = g1[now][i].to, w = g1[now][i].w;
if(dis[to] > dis[now]+w)
{
dis[to] = dis[now]+w;
a.push(make_pair(dis[to], to));
}
}
}
}
signed main()
{
cin >> n >> m1 >> m2 >> s;
for(int i = 1; i <= n; i++)
fa[i] = i;
int u, v, w;
for(int i = 1; i <= m1; i++)
{
scanf("%d%d%d", &u, &v, &w);
g1[u].push_back(make_pair(v, w));
g1[v].push_back(make_pair(u, w));
int fu = find(u), fv = find(v);
if(fu != fv) fa[fu] = fv;
}
for(int i = 1; i <= m2; i++)
{
scanf("%d%d%d", &u, &v, &w);
int fu = find(u), fv = find(v);
g2[u].push_back(make_pair(v, w));
in[fv]++;
}
queue<int> q;//拓扑排序的队列
for(int i = 1; i <= n; i++)
{
dis[i] = inf;
ele[find(i)].push_back(i);
if(fa[i] == i && in[i] == 0)
q.push(i);
}
dis[s] = 0;
while(q.size())
{
int now = q.front();
q.pop();
dijkstra(now);
for(int i = 0; i < ele[now].size(); i++)//枚举连通块内点
{
if(g2[ele[now][i]].size())
{
for(int j = 0; j < g2[ele[now][i]].size(); j++)
{
int to = g2[ele[now][i]][j].to, w = g2[ele[now][i]][j].w;
if(dis[ele[now][i]] != inf)//很重要的细节,防止用inf更新其他连通块
dis[to] = min(dis[to], dis[ele[now][i]]+w);
in[find(to)]--;
if(in[find(to)] == 0)
q.push(find(to));
}
}
}
}
for(int i = 1; i <= n; i++)
if(dis[i] != inf)
printf("%d\n", dis[i]);
else
puts("NO PATH");
return 0;
}