题目链接 https://www.acwing.com/problem/content/344/
题目描述
农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。
他想把牛奶送到T个城镇,编号为1~T。
这些城镇之间通过R条道路 (编号为1到R) 和P条航线 (编号为1到P) 连接。
每条道路 i 或者航线 i 连接城镇Ai到Bi,花费为Ci。
对于道路,0≤Ci≤10,000;然而航线的花费很神奇,花费CiCi可能是负数(−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”。
题解:
首先%%%lydrainbow
明确说了, 有负边,因此Dijkstra他GG了,但是吧,有SPFA。由于lydrainbow出数据的时候对本题数据进行了特殊构造,因此SPFA他也GG了。题目中表明,负边只有在单向边才会有,对于一个DAG,求最短路可以根据拓扑序在线性时间求解,其实就是按照拓扑序走一遍更新距离,走完之后的距离就是最近的。
这个方法可以应用到本题,但是本题的图并不是一个DAG怎么办???首先既然双向边没有负边,可以先将双向边读入,构成一个无向图,然后求无向图的连通块,将每一个连通块缩成一个点,在读入有向边,就得到了一个DAG。对于这个DAG可以通过拓扑序求最短路,而每一个连通块内的点可以通过堆优化的Dijkstra求最短路,其实也很简单,就是在求拓扑序的过程中求最短路。
首先找DAG中所有入度为0的点所在的连通块以及S(起点)所在的连通块入队(要求拓扑序了),然后依次从队列中取出一个点(这个点表示一个连通块),然后找属于这个连通块点,求他们的最短路。如果求解过程中遇到一个点同样属于这个连通块,就放入到堆中继续求解,如果不在这个连通块内,说明是单向边,就将这个点所在的连通块的入度减去1,如果入度为0,则入队(求解拓扑序的过程)。
/**
* Author : correct
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
struct p{
int x, dist;
p(int x, int dist){this->x = x; this->dist = dist;}
p(){}
bool operator < (const p& a)const{return this->dist < a.dist;}
bool operator > (const p& a)const{return this->dist > a.dist;}
};
#define ll long long
#define DEBUG cout << "----------------------------------------------\n"
#define mem(a, b) memset(a, b, sizeof a)
const int N = 25100, M = 50100 * 3, INF = 0x3f3f3f3f;
int head[N], nex[M], to[M], ed[M], cnt, belong[N], scc;
bool vis[N];
int in[N];
ll d[N];
queue<int > q;
priority_queue<p, vector<p>, greater<p> > pq;
void add(int a, int b, int c){
++cnt;
ed[cnt] = c;
to[cnt] = b;
nex[cnt] = head[a];
head[a] = cnt;
}
void dfs(int x){
belong[x] = scc;
for (int i = head[x]; i; i = nex[i]){
int y = to[i];
if (!belong[y])dfs(y);
}
}
int T, R, P, S;
int main(){
freopen("in.in", "r", stdin);
cnt = 0;
scc = 0;
mem(d, 0x3f);
#ifdef DEBUG
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
#endif
cin >> T >> R >> P >> S;
d[S] = 0;
for (int i = 1; i <= R; i++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
for (int i = 1; i <= T; i++){
if (!belong[i]){
++scc;
dfs(i);
}
}
for (int i = 1; i <= P; i++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
in[belong[b]]++;
}
q.push(belong[S]);
for (int i = 1; i <= scc; i++){
if (in[i] == 0)q.push(i);
}
while (q.size()){
int ft = q.front();
q.pop();
for (int i = 1; i <= T; i++){
if (belong[i] == ft){
pq.push(p(i, d[i]));
}
}
while (pq.size()){
p t = pq.top();
pq.pop();
if (vis[t.x])continue;
vis[t.x] = 1;
for (int i = head[t.x]; i; i = nex[i]){
int y = to[i];
int c = ed[i];
if (d[y] > d[t.x] + c){
d[y] = d[t.x] + c;
if (belong[y] == belong[t.x]){
pq.push(p(y, d[y]));
}
}
if (belong[y] != belong[t.x]){
in[belong[y]]--;
if (in[belong[y]] == 0){
q.push(belong[y]);
}
}
}
}
}
for (int i = 1; i <= T; i++){
if (d[i] >= INF)cout << "NO PATH\n";
else cout << d[i] << "\n";
}
return 0;
}
我查错查了一下午,发现错在了一个很沙雕的地方。第一步找入度为0的点的所在的连通块的时候,应该遍历所有的点,我遍历了,但是循环变量 i 被我用一个其他的非循环变量代替了。