๋ฌธ์ œ ๋งํฌ

์ด๋ฒˆ ๋ฌธ์ œ๋Š” ๋ฒจ๋งŒ-ํฌ๋“œ๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” SPFA ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด ๋‚œ์ด๋„๊ฐ€ ์–ด๋ ต์ง€ ์•Š์€ ๋ฌธ์ œ์ง€๋งŒ ํ’€์–ด๋ณด์•˜๋‹ค.

SPFA๋ฅผ ๋นจ๋ฆฌ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์ง€๋งŒ ์šฐ์„  ๋ฒจ๋งŒํฌ๋“œ์˜ ํ•™์Šต์ด ์„ ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ๋ฒจ๋งŒํฌ๋“œ๋ถ€ํ„ฐ ์ •๋ฆฌํ•ด๋ณด์ž.

๋ฒจ๋งŒํฌ๋“œ์˜ ํŠน์ง•

  1. 1:n ์ตœ๋‹จ๊ฑฐ๋ฆฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‹ค.
  2. ๋‹ค์ต์ŠคํŠธ๋ผ๋ณด๋‹ค ๋Š๋ฆฌ๋‹ค.
    • ๋‹ค์ต์ŠคํŠธ๋ผ๋Š” O(nlogn), ๋ฒจ๋งŒํฌ๋“œ๋Š” O(V*E)
    • ๋ชจ๋“  ๋…ธ๋“œ์— ๋Œ€ํ•ด ๋ชจ๋“  ๊ฐ„์„ ์„ ๊ฒ€์‚ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  3. ์Œ์ˆ˜๊ฐ„์„ ์ด ์žˆ๋”๋ผ๋„ ์ž‘๋™ํ•œ๋‹ค!
    • ๋‹ค์ต์ŠคํŠธ๋ผ์˜ ๊ฒฝ์šฐ ์Œ์ˆ˜๊ฐ„์„ ์ด ํฌํ•จ๋˜๋ฉด ์˜ค๋‹ต์„ ๋‚ด๋†“์„ ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊ธด๋‹ค.
    • ๋Š๋ฆฌ์ง€๋งŒ ๋ฒจ๋งŒํฌ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ๋ฐ–์— ์—†๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ.
  4. โ€œ์Œ์ˆ˜ ์‚ฌ์ดํดโ€์„ ๊ฒ€์ถœํ•  ์ˆ˜ ์žˆ๋‹ค!
    • ์Œ์ˆ˜ ์‚ฌ์ดํด์€ n๋ฒˆ ๋…ธ๋“œ์—์„œ ์ž๊ธฐ์ž์‹ ์œผ๋กœ ๋Œ์•„์˜ฌ ๋•Œ ๊ฐ€์ค‘์น˜๊ฐ€ ์Œ์ˆ˜๊ฐ€ ๋˜๋Š” ์‚ฌ์ดํด์„ ๋งํ•œ๋‹ค.
    • n๋ฒˆ๋…ธ๋“œ์—์„œ n๋ฒˆ๋…ธ๋“œ๋กœ ๊ฐ€๋Š” ๊ฑฐ๋ฆฌ๊ฐ€ 0 ์ดํ•˜์ธ ์Œ์ˆ˜๋ผ๋ฉด, ์ตœ๋‹จ๊ฑฐ๋ฆฌ๋Š” ์–ด๋–ป๊ฒŒ ๋˜๊ฒ ๋Š”๊ฐ€?
    • ์Œ์ˆ˜ ๋ฌดํ•œ๋Œ€ ๊ฐ’์ด ๋˜๋ฏ€๋กœ, ๊ฒ€์ถœ๋งŒ ํ•˜๋ฉด ์ปดํ“จํ„ฐ๋Š” ํ•  ์ผ์„ ๋‹ค ํ•œ ๊ฒƒ์ด๋‹ค.

๋ฌธ์ œ ํ’€์ด (๋ฒจ๋งŒ-ํฌ๋“œ)

package ์ž์œจํŒ€์Šคํ„ฐ๋””.d240928;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
 
public class BOJ_1865 {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static StringTokenizer st;
	static int TC, n, m, w;
	static List<int[]> graph;
	static int[] dist;
 
	private static void input() throws IOException {
		st = new StringTokenizer(br.readLine());
		n = Integer.parseInt(st.nextToken());
		m = Integer.parseInt(st.nextToken());
		w = Integer.parseInt(st.nextToken());
		graph = new ArrayList<>();
		dist = new int[n + 1];
		Arrays.fill(dist, Integer.MAX_VALUE);
 
		int s, e, t;
		for (int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			s = Integer.parseInt(st.nextToken());
			e = Integer.parseInt(st.nextToken());
			t = Integer.parseInt(st.nextToken());
			graph.add(new int[] {s, e, t});
			graph.add(new int[] {e, s, t});
		}
		for (int i = 0; i < w; i++) {
			st = new StringTokenizer(br.readLine());
			s = Integer.parseInt(st.nextToken());
			e = Integer.parseInt(st.nextToken());
			t = Integer.parseInt(st.nextToken());
			graph.add(new int[] {s, e, -t});
		}
	}
 
	private static boolean bellmanFord(int start) {
		dist[start] = 0;
		int now, next, cost, nextCost;
		// ์ตœ์†Œ๊ฐ’ ๊ฐฑ์‹ . ๋ฆด๋ž™์„ธ์ด์…˜
		for (int v = 1; v <= n; v++) {
			for (int e = 0; e < graph.size(); e++) {
				now = graph.get(e)[0];
				next = graph.get(e)[1];
				cost = graph.get(e)[2];
				nextCost = dist[now] + cost;
				if (dist[now] != Integer.MAX_VALUE && dist[next] > nextCost) {
					dist[next] = nextCost;
				}
			}
		}
		// ์Œ์ˆ˜ ์‚ฌ์ดํด ์กด์žฌ ํ™•์ธ
		for (int e = 0; e < graph.size(); e++) {
			now = graph.get(e)[0];
			next = graph.get(e)[1];
			cost = graph.get(e)[2];
			nextCost = dist[now] + cost;
			if (dist[now] != Integer.MAX_VALUE && dist[next] > nextCost) {
				return true;
			}
		}
		return false;
	}
 
 
	public static void main(String[] args) throws Exception {
		TC = Integer.parseInt(br.readLine());
		boolean ans = false;
		for (int testCase = 0; testCase < TC; testCase++) {
			input();
			for (int i = 1; i <= n; i++) {
				if (dist[i] == Integer.MAX_VALUE) {
					if (ans = bellmanFord(i)) {
						System.out.println("YES");
						break;
					}
				}
			}
			if (!ans) {
				System.out.println("NO");
			}
		}
	}
 
 
	class Node {
		int v, e;
		public Node(int v, int e) {
			this.v = v;
			this.e = e;
		}
	}
}

๋ฒจ๋งŒ ํฌ๋“œ๋ฅผ ์ดํ•ดํ•จ์— ์žˆ์–ด์„œ ์œ„ ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ๋ณผ ํ•„์š”๋Š” ์—†๋‹ค. ๋‹ค์ต์ŠคํŠธ๋ผ์™€ ์„ธํŒ…์„ ๋‹ค๋ฅด๊ฒŒ ํ•  ๊ฒƒ์€, ์—ฐ๊ฒฐ ๊ฐ„์„ ์„ (์ถœ๋ฐœ๋…ธ๋“œ, ๋„์ฐฉ๋…ธ๋“œ, ๊ฐ€์ค‘์น˜)๋กœ ์ €์žฅํ•œ๋‹ค๋Š” ๊ฒƒ.

  • for (int v = 1; v <= n; v++) ๋ฃจํ”„ ๋‚ด๋ถ€์—์„œ ๋ชจ๋“  ๊ฐ„์„ ์„ ํ†ตํ•ด ์ตœ๋‹จ๊ฑฐ๋ฆฌ๋ฅผ ๊ฐฑ์‹ ํ•ด์ฃผ๋Š” ๊ณผ์ •์„ โ€œ๋ฆด๋ ‰์„ธ์ด์…˜โ€์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
  • ๋ฆด๋ ‰์„ธ์ด์…˜์„ ๋…ธ๋“œ์˜ ๊ฐฏ์ˆ˜๋งŒํผ ์‹คํ–‰ํ•ด ์ค€๋‹ค.
  • ์œ„ ๊ณผ์ •์„ ๋งˆ์นœ ํ›„, ํ•œ๋ฒˆ์˜ ๋ฆด๋ ‰์„ธ์ด์…˜์„ ๋” ์ง„ํ–‰ํ•˜๋‹ค ์ตœ์†Ÿ๊ฐ’ ๊ฐฑ์‹ ์ด ๋‹ค์‹œ ๋ฐœ์ƒํ•˜๋ฉด ์ด๋Š” ์Œ์ˆ˜์‚ฌ์ดํด์„ ํฌํ•จํ•œ ๊ทธ๋ž˜ํ”„์ด๋‹ค.
    • ์ง๊ด€์ ์œผ๋กœ ์ƒ๊ฐํ•ด๋ด๋„, ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ ๊ฑฐ์ณ๊ฐ€๋Š” ๊ฒƒ ๋ณด๋‹ค ๋” ๊ธด ๊ฑฐ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋” ์งง์€ ๊ฑฐ๋ฆฌ๊ฐ€ ๋‚˜์˜จ๋‹ค๋ฉดโ€ฆ ์Œ์ˆ˜ ์‚ฌ์ดํด์„ ํ•„์—ฐ์ ์œผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.

SPFA (Shortest Path Faster Algorithm) ํŠน์ง•

  • ๋“œ๋””์–ด SPFA์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.
  1. ๋ฒจ๋งŒ-ํฌ๋“œ์˜ ๋ฒ ์ด์Šค๋ฅผ ๊ฐ€์ง„๋‹ค.
  2. ๋ฒจ๋งŒ-ํฌ๋“œ์—์„œ ๋ชจ๋“  ๊ฐ„์„ ์„ ํ†ตํ•ด ๋ฆด๋ ‰์„ธ์ด์…˜์„ ํ•˜๋Š” ๊ณผ์ •์„ โ€œ๊ฐฑ์‹ ๋œ ๋…ธ๋“œ์™€ ์—ฐ๊ฒฐ๋œ ๊ทธ๋ž˜ํ”„๋งŒ์„ ํ†ตํ•ดโ€ ๋ฆด๋ ‰์„ธ์ด์…˜์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  3. ์ด๋Š” ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ผ ๋ฟ์ด๋ฉฐ, ์‚ฌ์‹ค ์ฝ”๋”ฉํ…Œ์ŠคํŠธ์—์„œ ์ฃผ๋Š” ์ตœ์•…์˜ ๊ฒฝ์šฐ์˜ ์ˆ˜ ์—์„œ๋Š” ์‹œ๊ฐ„๋ณต์žก๋„๊ฐ€ ์—ฌ์ „ํžˆ O(V * E)์ด๋‹ค.
    • ๋‹ค๋ฅด๊ฒŒ ๋งํ•˜๋ฉด ์ž˜๋ชป ๊ตฌํ˜„ํ•  ์‹œ ์›๋ž˜์˜ ๋ฒจ๋งŒํฌ๋“œ๋ณด๋‹ค ๋Š๋ ค์ง„๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด Queue์™€ ๊ฐ™์€ ๋‹จ์ˆœํ•œ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์ž.
    • SPFA ๊ตฌํ˜„๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ณ  ์Šค์Šค๋กœ ์ตœ์ ํ™”๋ฅผ ํ–ˆ์„ ๋•Œ, Set์„ ์‚ฌ์šฉํ•˜๋Š๋ผ ์˜คํžˆ๋ ค ๋Š๋ ค์กŒ๋˜ ๊ฒฝํ—˜์ด ์žˆ๋‹ค

๋ฌธ์ œ ํ’€์ด (SPFA)

package ์ž์œจํŒ€์Šคํ„ฐ๋””.d240928;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
 
public class BOJ_1865_SPFA {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static StringTokenizer st;
	static int TC, n, m, w;
	static List<int[]> graph[];
	static int[] dist;
 
	private static void input() throws IOException {
		st = new StringTokenizer(br.readLine());
		n = Integer.parseInt(st.nextToken());
		m = Integer.parseInt(st.nextToken());
		w = Integer.parseInt(st.nextToken());
		graph = new ArrayList[n + 1];
		for (int i = 0; i <= n; i++)
			graph[i] = new ArrayList<>();
		dist = new int[n + 1];
		Arrays.fill(dist, Integer.MAX_VALUE);
 
		int s, e, t;
		for (int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			s = Integer.parseInt(st.nextToken());
			e = Integer.parseInt(st.nextToken());
			t = Integer.parseInt(st.nextToken());
			graph[s].add(new int[] {e, t});
			graph[e].add(new int[] {s, t});
		}
		for (int i = 0; i < w; i++) {
			st = new StringTokenizer(br.readLine());
			s = Integer.parseInt(st.nextToken());
			e = Integer.parseInt(st.nextToken());
			t = Integer.parseInt(st.nextToken());
			graph[s].add(new int[] {e, -t});
		}
	}
 
	private static boolean spfa(int start) {
		dist[start] = 0;
		// ์ตœ์†Œ๊ฐ’ ๊ฐฑ์‹ . ๋ฆด๋ž™์„ธ์ด์…˜
		Queue<Integer> q = new ArrayDeque<>();
		boolean[] inque = new boolean[n+1];
		int[] cnt = new int[n+1];
		q.add(start);
		inque[start] = true;
 
		int now, next, cost;
		while (!q.isEmpty()) {
			now = q.poll();
			inque[now] = false;
			for (int[] edge : graph[now]) {
				next = edge[0];
				cost = edge[1];
 
				if (dist[next] > dist[now] + cost) {
					dist[next] = dist[now] + cost;
					if (!inque[next]) {
						q.add(next);
						inque[next] = true;
						cnt[next]++;
						if (cnt[next] > n)
							return true;
					}
				}
			}
		}
 
		return false;
	}
 
 
	public static void main(String[] args) throws Exception {
		TC = Integer.parseInt(br.readLine());
		boolean ans = false;
		for (int testCase = 0; testCase < TC; testCase++) {
			input();
			for (int i = 1; i <= n; i++) {
				if (dist[i] == Integer.MAX_VALUE) {
					if (ans = spfa(i)) {
						System.out.println("YES");
						break;
					}
				}
			}
			if (!ans) {
				System.out.println("NO");
			}
		}
	}
 
 
	class Node {
		int v, e;
		public Node(int v, int e) {
			this.v = v;
			this.e = e;
		}
	}
}

๋‹ฌ๋ผ์ง„ ๋ถ€๋ถ„๋งŒ ๋ณด์ž.

  1. input์—์„œ ํŠน์ • ๋…ธ๋“œ์—์„œ ์ถœ๋ฐœํ•˜๋Š” ๊ฐ„์„ ์„ ๋น ๋ฅด๊ฒŒ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ„์„ ์„ ์ž…๋ ฅ๋ฐ›์•˜๋‹ค.
  2. BellmanFord โ†’ SPFA๋กœ ๋„˜์–ด๊ฐ€๋ฉฐ, ๊ฐฑ์‹ ๋œ ๋…ธ๋“œ๋งŒ์„ ์ฒดํฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด queue๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.
  • queue๋Š” ์ž์ฒด์  ์ค‘๋ณต๋ฐฉ์ง€๊ฐ€ ์—†๋‹ค. ๋…ธ๋“œ๊ฐ€ queue์— ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•ด์ฃผ๋Š” inque๋ฐฐ์—ด์„ ๋งŒ๋“ค์—ˆ๋‹ค.
  • ๊ณ„์‚ฐ ํšŸ์ˆ˜๊ฐ€ ๋…ธ๋“œ์˜ ์ˆ˜๋ฅผ ๋„˜์–ด๊ฐ€๋Š”์ง€ ์ฒดํฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด cnt ๋ฐฐ์—ด์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์Œ์ˆ˜ ์‚ฌ์ดํด์„ ๊ฒ€์ถœํ•œ๋‹ค.

์—ฐ๊ฒฐ๋œ ๊ฐ„์„ ์„ ํ†ตํ•ด ์ตœ์†Œ๊ฐ’์ด ๊ฐฑ์‹ ๋œ ๋…ธ๋“œ๋งŒ์ด ๋‹ค์Œ ๋…ธ๋“œ๋กœ ์—ฐ๊ฒฐ๋˜๋ฉฐ ์ตœ์†Œ๊ฐ’์„ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ๋‹ค! ๊ทธ๋ž˜์„œ ๊ฐฑ์‹ ๊ณผ ํ•จ๊ป˜ ํ์— ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

์ˆ˜ํ™•

๋ฉ”๋ชจ๋ฆฌ๋Š” 17๋ฉ”๊ฐ€๋ฐ”์ดํŠธ ์ฆ๊ฐ€ํ–ˆ์ง€๋งŒ ์‹œ๊ฐ„์„ ์•ฝ 200ms ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ดœ์ฐฎ์€ trade-off ๊ด€๊ณ„์ธ ๊ฒƒ ๊ฐ™๋‹ค.

ํ›„๊ธฐ

  • ๋ฌธ์ œ์— ์•ฝ๊ฐ„์˜ ํ•จ์ •์ด ์žˆ๋‹ค๋ฉด, ๋ชจ๋“  ๋…ธ๋“œ๋“ค์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ์—, ํ•„์š”์— ๋”ฐ๋ผ ์ตœ๋‹จ๊ฑฐ๋ฆฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

  • SPFA๋ฅผ ๋ชฐ๋ž์„ ๋•Œ, ๋ฒจ๋งŒํฌ๋“œ๊ฐ€ ๋ชจ๋“  ๊ฐ„์„ ์„ ๋Œ์•„์•ผํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์ด ๋„ˆ๋ฌด ๋น„ํšจ์œจ์ ์ด๋ผ ์ƒ๊ฐ์ด ๋“ค์–ด ์—ฐ๊ฒฐ๋œ ๊ฐ„์„ ๋งŒ์„ ํ†ตํ•ด ๊ฐฑ์‹ ์ด ์ˆ˜ํ–‰๋˜๋„๋ก ๋งŒ๋“ค๋‹ค๊ฐ€ set์„ ์ผ๋‹ค.

    • ํ์— ๊ฐ™์€ ๋…ธ๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ๊ฒŒ ์‹ซ์—ˆ๊ณ , set๋„ enhanced for๋ฌธ์„ ํ†ตํ•ด ์ˆœํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ์— ๊ทธ๋Ÿฐ ์„ ํƒ์„ ํ–ˆ๋‹ค.
    • ๊ฒฐ๊ณผ๋Š” ๋Œ€์‹คํŒจ์˜€๋‹ค. ๊ทธ๋•Œ๋Š” ์‹œ๊ฐ„์ด ์˜คํžˆ๋ ค ๋Š˜์—ˆ๋‹ค.
    • ์ข€ ๋” ์ฐพ์•„๋ณด๊ณ  ๋‚˜์„œ๋Š” Queue์™€ ์ค‘๋ณต์ฒดํฌ ๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๊ณต์ ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.
      • ์ตœ์•…์˜ ๊ฒฝ์šฐ์— ์‹œ๊ฐ„์„ ์ค„์—ฌ์ฃผ์ง€ ๋ชปํ•˜๊ธฐ์—, SPFA ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ๊ตฌํ˜„์„ ์ •๊ตํ•˜๊ฒŒ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐฐ์› ๋‹ค. (๋‹คํ–‰์ธ๊ฑด ๋ณ„๋กœ ์–ด๋ ต์ง€๋Š” ์•Š๋‹ค)
  • ์ด๋ ‡๊ฒŒ ํ•œ๋ฒˆ ๊ตฌํ˜„์„ ํ•ด๋†“๊ณ  ๋‚˜๋‹ˆ, ์•ž์œผ๋กœ ๋ฒจ๋งŒ ํฌ๋“œ๊ฐ€ ํ•„์š”ํ•œ ๋ฌธ์ œ์—์„œ๋Š” ๋งˆ์Œ๊ป ์‘์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

    • ๋น„๋ก ๊ทธ๋Ÿฐ ๋ฌธ์ œ์˜ ์ˆ˜๊ฐ€ ๋งŽ์ง€๋Š” ์•Š์ง€๋งŒ ๐Ÿ˜