【图论】C1168. 简单可有负环单源最短路径问题(起点 × spfa)

一、题目描述

输入数据给出一个有 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;
		}
	} 
}

复杂度分析

  • 时间复杂度: O ( E ) O(E)
  • 空间复杂度: O ( E + V ) O(E+V)

方法二: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 到的技能:

  • 所有点一起进队列跑出来的效果和一个一个跑是一样的。
  • 需要注意题目有没有明确图是连通的。
发布了691 篇原创文章 · 获赞 151 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/105499726
今日推荐