一、题目描述
输入数据给出一个有 N 个节点,M 条边的带权有向图。
要求你写一个程序,判断这个有向图中是否存在负权回路。
如果从一个点沿着某条路径出发,又回到了自己,而且所经过的边上的权和小于0,就说这条路是一个负权回路。
- 如果存在负权回路,只输出一行 −1;
- 如果不存在负权回路,再求出一个点 S 到每个点的最短路的长度。
- 约定: S 到 S 的距离为 0,如果 S 与这个点不连通, 则输出NoPath。
输入格式
-
第一行三个正整数,分别为点数 N,边数 M,源点 S;
-
以下 M 行,每行三个整数 a,b,c,表示点 a,b 之间连有一条边,权值为 c。
-
图中点的编号从 1 到 N。
输出格式
- 如果存在负权环,只输出一行 −1,否则按以下格式输出:
- 共 N 行,第 i 行描述 S 点到点 i 的最短路:
- 如果 S 与 i 不连通,输出 NoPath;
- 如果 i=S,输出 0。
- 其他情况输出 S 到 i 的最短路的长度。
数据范围
- 2≤N≤1000,
1≤M≤105,
1≤a,b,S≤N,
|c|≤106
输入样例:
6 8 1
1 3 4
1 2 6
3 4 -7
6 4 2
2 4 5
3 6 3
4 5 1
3 5 4
输出样例:
0
6
4
-3
-2
7
二、题解
方法一:SPFA
先让所有点作为起点,判断一下是否有负环:
- 如果没有,则继续以起点 S 跑一边 spfa。
- 有则输出 -1 结束战斗。
最后的样例错误:不知何原因。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int V, E;
static int INF = 0x3f3f3f3f;
static int MAXN = (int)1e5 + 50;
static Edge[] edges;
static int[] dist, count, head;
static int tot;
static boolean[] inq;
static void addEdge(int u, int v, int w) {
edges[++tot] = new Edge();
edges[tot].to = v;
edges[tot].w = w;
edges[tot].next = head[u];
head[u] = tot;
}
static boolean spfa(int S) {
inq = new boolean[MAXN];
count = new int[MAXN];
dist = new int[MAXN];
Arrays.fill(dist, INF);
dist[S] = 0;
Queue<Integer> q = new ArrayDeque<>();
q.add(S);
inq[S] = true;
while (!q.isEmpty()) {
int v = q.poll();
inq[v] = false;
for (int i = head[v]; i != 0; i = edges[i].next) {
int to = edges[i].to, w = edges[i].w;
if (dist[to] > dist[v] + edges[i].w) {
count[to] = count[i] + 1;
if (count[to] > V)
return true;
dist[to] = dist[v] + edges[i].w;
if (!inq[to]) {
q.add(to);
inq[to] = true;
}
}
}
}
return false;
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
V = sc.nextInt();
E = sc.nextInt();
int S = sc.nextInt();
inq = new boolean[MAXN];
count = new int[MAXN];
dist = new int[MAXN];
edges = new Edge[MAXN];
head = new int[MAXN];
for (int i = 1; i <= E; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
addEdge(a, b, c);
}
for (int i = 1; i <= V; i++) { //bug01
if (spfa(i)) {
System.out.println(-1);
return;
}
}
boolean res = spfa(S);
if (res) {System.out.println(-1);return;}
for (int i = 1; i <= V; i++) {
if (dist[i] == INF)
System.out.println("NoPath");
else
System.out.println(dist[i]);
}
}
static class Edge {
int to, w, next;
Edge() {}
Edge(int to, int w, int next) {
this.to = to;
this.w = w;
this.next = next;
}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法二:spfa + List
上面的尝试,我们发现几个 bug,我们虽然说是以每个点为起点跑了一篇 spfa,但是他们是各自跑的,并非全部加到队列里一起跑。
存在的疑惑:
- Q1:但是这样做,样例为什么可以过呢?
A1: - Q2:请问为什么要以所有点作为起点跑一边呢?判负环跑一个点不行吗?
A2:因为图不保证连通,有可能存在有负环,但图不连通。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int V, E;
static int INF = 0x3f3f3f3f;
static int MAXN = (int)1e5 + 50;
static Edge[] edges;
static int[] dist, count, head;
static int tot;
static boolean[] inq;
static void addEdge(int u, int v, int w) {
edges[++tot] = new Edge();
edges[tot].to = v;
edges[tot].w = w;
edges[tot].next = head[u];
head[u] = tot;
}
static boolean spfa( List<Integer> list) {
inq = new boolean[MAXN];
count = new int[MAXN];
dist = new int[MAXN];
Arrays.fill(dist, INF);
Queue<Integer> q = new ArrayDeque<>();
for (int i = 0; i < list.size(); i++) {
q.add(list.get(i));
inq[list.get(i)] = true;
dist[list.get(i)] = 0;
count[list.get(i)] = 1;
}
while (!q.isEmpty()) {
int v = q.poll();
inq[v] = false;
for (int i = head[v]; i != 0; i = edges[i].next) {
int to = edges[i].to, w = edges[i].w;
if (dist[to] > dist[v] + w) {
count[to] = count[v] + 1;
if (count[to] > V)
return true;
dist[to] = dist[v] + w;
if (!inq[to]) {
q.add(to);
inq[to] = true;
}
}
}
}
return false;
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
V = sc.nextInt();
E = sc.nextInt();
int S = sc.nextInt();
edges = new Edge[MAXN];
head = new int[MAXN];
for (int i = 1; i <= E; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
addEdge(a, b, c);
}
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= V; i++) {
list.add(i);
}
if (spfa(list)){
System.out.println(-1);
return;
}
list = new ArrayList<>();
list.add(S);
spfa(list);
for (int i = 1; i <= V; i++) {
if (dist[i] == INF)
System.out.println("NoPath");
else
System.out.println(dist[i]);
}
}
static class Edge {
int to, w, next;
Edge() {}
}
}
get 到的技能:
- 所有点一起进队列跑出来的效果和一个一个跑是一样的。
- 需要注意题目有没有明确图是连通的。