1"""
2๊ฐํ ์ฐ๊ฒฐ ์์ (SCC - Strongly Connected Components)
3Strongly Connected Components
4
5๋ฐฉํฅ ๊ทธ๋ํ์์ ์๋ก ๋๋ฌ ๊ฐ๋ฅํ ์ ์ ๋ค์ ์ต๋ ์งํฉ์ ์ฐพ์ต๋๋ค.
6"""
7
8from typing import List, Tuple, Set
9from collections import defaultdict
10
11
12# =============================================================================
13# 1. Kosaraju ์๊ณ ๋ฆฌ์ฆ
14# =============================================================================
15
16def kosaraju_scc(n: int, edges: List[Tuple[int, int]]) -> List[List[int]]:
17 """
18 Kosaraju ์๊ณ ๋ฆฌ์ฆ์ผ๋ก SCC ์ฐพ๊ธฐ
19 ์๊ฐ๋ณต์ก๋: O(V + E)
20 ๊ณต๊ฐ๋ณต์ก๋: O(V + E)
21
22 1. ์ ๋ฐฉํฅ DFS๋ก ์ข
๋ฃ ์์ ์คํ ๊ตฌ์ฑ
23 2. ์ญ๋ฐฉํฅ ๊ทธ๋ํ์์ ์คํ ์์๋๋ก DFS
24 """
25 # ์ ๋ฐฉํฅ/์ญ๋ฐฉํฅ ๊ทธ๋ํ
26 graph = defaultdict(list)
27 reverse_graph = defaultdict(list)
28
29 for u, v in edges:
30 graph[u].append(v)
31 reverse_graph[v].append(u)
32
33 # 1๋จ๊ณ: ์ ๋ฐฉํฅ DFS๋ก ์ข
๋ฃ ์์ ๊ธฐ๋ก
34 visited = [False] * n
35 finish_stack = []
36
37 def dfs1(node: int):
38 visited[node] = True
39 for neighbor in graph[node]:
40 if not visited[neighbor]:
41 dfs1(neighbor)
42 finish_stack.append(node)
43
44 for i in range(n):
45 if not visited[i]:
46 dfs1(i)
47
48 # 2๋จ๊ณ: ์ญ๋ฐฉํฅ DFS๋ก SCC ์ฐพ๊ธฐ
49 visited = [False] * n
50 sccs = []
51
52 def dfs2(node: int, component: List[int]):
53 visited[node] = True
54 component.append(node)
55 for neighbor in reverse_graph[node]:
56 if not visited[neighbor]:
57 dfs2(neighbor, component)
58
59 while finish_stack:
60 node = finish_stack.pop()
61 if not visited[node]:
62 component = []
63 dfs2(node, component)
64 sccs.append(component)
65
66 return sccs
67
68
69# =============================================================================
70# 2. Tarjan ์๊ณ ๋ฆฌ์ฆ
71# =============================================================================
72
73def tarjan_scc(n: int, edges: List[Tuple[int, int]]) -> List[List[int]]:
74 """
75 Tarjan ์๊ณ ๋ฆฌ์ฆ์ผ๋ก SCC ์ฐพ๊ธฐ
76 ์๊ฐ๋ณต์ก๋: O(V + E)
77 ๊ณต๊ฐ๋ณต์ก๋: O(V)
78
79 low[v] = v์์ ๋๋ฌ ๊ฐ๋ฅํ ์ต์ ๋ฐ๊ฒฌ ์๊ฐ
80 """
81 graph = defaultdict(list)
82 for u, v in edges:
83 graph[u].append(v)
84
85 disc = [-1] * n # ๋ฐ๊ฒฌ ์๊ฐ
86 low = [-1] * n # low-link ๊ฐ
87 on_stack = [False] * n
88 stack = []
89 sccs = []
90 time = [0] # ์ ์ญ ์๊ฐ
91
92 def dfs(node: int):
93 disc[node] = low[node] = time[0]
94 time[0] += 1
95 stack.append(node)
96 on_stack[node] = True
97
98 for neighbor in graph[node]:
99 if disc[neighbor] == -1: # ๋ฏธ๋ฐฉ๋ฌธ
100 dfs(neighbor)
101 low[node] = min(low[node], low[neighbor])
102 elif on_stack[neighbor]: # ์คํ์ ์์ (back edge)
103 low[node] = min(low[node], disc[neighbor])
104
105 # SCC ๋ฃจํธ ๋ฐ๊ฒฌ
106 if low[node] == disc[node]:
107 component = []
108 while True:
109 v = stack.pop()
110 on_stack[v] = False
111 component.append(v)
112 if v == node:
113 break
114 sccs.append(component)
115
116 for i in range(n):
117 if disc[i] == -1:
118 dfs(i)
119
120 return sccs
121
122
123# =============================================================================
124# 3. SCC ์ถ์ฝ ๊ทธ๋ํ (DAG)
125# =============================================================================
126
127def build_scc_dag(n: int, edges: List[Tuple[int, int]]) -> Tuple[List[List[int]], List[List[int]], List[int]]:
128 """
129 SCC๋ฅผ ์ฐพ๊ณ ์ถ์ฝ ๊ทธ๋ํ(DAG) ๊ตฌ์ฑ
130 ๋ฐํ: (sccs, scc_graph, node_to_scc)
131 """
132 sccs = tarjan_scc(n, edges)
133
134 # ๊ฐ ๋
ธ๋๊ฐ ์ํ SCC ๋งคํ
135 node_to_scc = [-1] * n
136 for i, component in enumerate(sccs):
137 for node in component:
138 node_to_scc[node] = i
139
140 # SCC ๊ฐ์ ๊ฐ์ (DAG)
141 scc_edges = set()
142 for u, v in edges:
143 scc_u = node_to_scc[u]
144 scc_v = node_to_scc[v]
145 if scc_u != scc_v:
146 scc_edges.add((scc_u, scc_v))
147
148 scc_graph = defaultdict(list)
149 for u, v in scc_edges:
150 scc_graph[u].append(v)
151
152 return sccs, dict(scc_graph), node_to_scc
153
154
155# =============================================================================
156# 4. 2-SAT ๋ฌธ์
157# =============================================================================
158
159class TwoSAT:
160 """
161 2-SAT ๋ฌธ์ ํด๊ฒฐ๊ธฐ
162 n๊ฐ์ ๋ถ๋ฆฐ ๋ณ์์ 2-CNF ์กฐ๊ฑด์
163
164 ๋ณ์ x_i: ๋
ธ๋ 2*i (true), ๋
ธ๋ 2*i+1 (false)
165 ์กฐ๊ฑด (a โจ b): ยฌa โ b, ยฌb โ a
166 """
167
168 def __init__(self, n: int):
169 self.n = n
170 self.graph = defaultdict(list)
171 self.reverse_graph = defaultdict(list)
172
173 def _var(self, x: int, negated: bool) -> int:
174 """๋ณ์๋ฅผ ๊ทธ๋ํ ๋
ธ๋๋ก ๋ณํ"""
175 return 2 * x + (1 if negated else 0)
176
177 def _neg(self, node: int) -> int:
178 """๋
ธ๋์ ๋ถ์ """
179 return node ^ 1
180
181 def add_clause(self, x: int, neg_x: bool, y: int, neg_y: bool):
182 """
183 ์กฐ๊ฑด ์ถ๊ฐ: (x โจ y)
184 neg_x: x๊ฐ ๋ถ์ ์ธ์ง
185 neg_y: y๊ฐ ๋ถ์ ์ธ์ง
186 """
187 # (x โจ y) โก (ยฌx โ y) โง (ยฌy โ x)
188 node_x = self._var(x, neg_x)
189 node_y = self._var(y, neg_y)
190
191 # ยฌx โ y
192 self.graph[self._neg(node_x)].append(node_y)
193 self.reverse_graph[node_y].append(self._neg(node_x))
194
195 # ยฌy โ x
196 self.graph[self._neg(node_y)].append(node_x)
197 self.reverse_graph[node_x].append(self._neg(node_y))
198
199 def solve(self) -> Tuple[bool, List[bool]]:
200 """
201 2-SAT ํด๊ฒฐ
202 ๋ฐํ: (๋ง์กฑ ๊ฐ๋ฅ ์ฌ๋ถ, ๊ฐ ๋ณ์์ ๊ฐ)
203 """
204 total_nodes = 2 * self.n
205
206 # Kosaraju๋ก SCC ์ฐพ๊ธฐ
207 visited = [False] * total_nodes
208 finish_stack = []
209
210 def dfs1(node: int):
211 visited[node] = True
212 for neighbor in self.graph[node]:
213 if not visited[neighbor]:
214 dfs1(neighbor)
215 finish_stack.append(node)
216
217 for i in range(total_nodes):
218 if not visited[i]:
219 dfs1(i)
220
221 visited = [False] * total_nodes
222 scc_id = [-1] * total_nodes
223 current_scc = 0
224
225 def dfs2(node: int):
226 visited[node] = True
227 scc_id[node] = current_scc
228 for neighbor in self.reverse_graph[node]:
229 if not visited[neighbor]:
230 dfs2(neighbor)
231
232 while finish_stack:
233 node = finish_stack.pop()
234 if not visited[node]:
235 dfs2(node)
236 current_scc += 1
237
238 # ๋ง์กฑ ๊ฐ๋ฅ์ฑ ๊ฒ์ฌ
239 for i in range(self.n):
240 if scc_id[2 * i] == scc_id[2 * i + 1]:
241 return False, []
242
243 # ํด ๊ตฌ์ฑ (๋์ค์ ๋ฐ๊ฒฌ๋ SCC = ๋ ์์ SCC ID = ๋ ๋์ ์์ ์์)
244 assignment = [False] * self.n
245 for i in range(self.n):
246 # scc_id๊ฐ ์์ผ๋ฉด ์์ ์์์์ ๋ค์ ์์ โ ๊ทธ ๊ฐ์ด true
247 assignment[i] = scc_id[2 * i] > scc_id[2 * i + 1]
248
249 return True, assignment
250
251
252# =============================================================================
253# 5. ์ค์ ๋ฌธ์ : ํ๊ต ๋๋ฌ ๊ฐ๋ฅ์ฑ
254# =============================================================================
255
256def min_roads_to_connect(n: int, roads: List[Tuple[int, int]]) -> int:
257 """
258 ๋ชจ๋ ํ๊ต๊ฐ ์๋ก ๋๋ฌ ๊ฐ๋ฅํ๊ฒ ํ๋ ค๋ฉด ์ถ๊ฐํด์ผ ํ ์ต์ ๋๋ก ์
259 = max(์ง์
์ฐจ์ 0์ธ SCC ์, ์ง์ถ ์ฐจ์ 0์ธ SCC ์)
260 (SCC๊ฐ 1๊ฐ๋ฉด 0)
261 """
262 if not roads:
263 return n - 1 if n > 1 else 0
264
265 sccs, scc_graph, node_to_scc = build_scc_dag(n, roads)
266
267 if len(sccs) == 1:
268 return 0
269
270 # ๊ฐ SCC์ ์ง์
/์ง์ถ ์ฐจ์
271 in_degree = [0] * len(sccs)
272 out_degree = [0] * len(sccs)
273
274 for scc_u, neighbors in scc_graph.items():
275 out_degree[scc_u] = len(neighbors)
276 for scc_v in neighbors:
277 in_degree[scc_v] += 1
278
279 # ์ง์
/์ง์ถ ์ฐจ์ 0์ธ SCC ๊ฐ์
280 sources = sum(1 for d in in_degree if d == 0)
281 sinks = sum(1 for d in out_degree if d == 0)
282
283 return max(sources, sinks)
284
285
286# =============================================================================
287# 6. ์ค์ ๋ฌธ์ : ์ค์ ๋
ธ๋ (Articulation Points ์ ์ฌ)
288# =============================================================================
289
290def find_critical_nodes(n: int, edges: List[Tuple[int, int]]) -> List[int]:
291 """
292 ์ ๊ฑฐํ๋ฉด SCC ๊ฐ์๊ฐ ์ฆ๊ฐํ๋ ๋
ธ๋๋ค ์ฐพ๊ธฐ
293 (๊ฐ๋จํ brute force ๊ตฌํ)
294 """
295 original_scc_count = len(tarjan_scc(n, edges))
296 critical = []
297
298 for remove_node in range(n):
299 # ํด๋น ๋
ธ๋ ์ ์ธํ ๊ทธ๋ํ
300 new_edges = [(u, v) for u, v in edges if u != remove_node and v != remove_node]
301
302 # ๋
ธ๋ ์ฌ๋งคํ
303 remaining = [i for i in range(n) if i != remove_node]
304 if not remaining:
305 continue
306
307 node_map = {old: new for new, old in enumerate(remaining)}
308 remapped_edges = [(node_map[u], node_map[v]) for u, v in new_edges
309 if u in node_map and v in node_map]
310
311 new_scc_count = len(tarjan_scc(len(remaining), remapped_edges))
312
313 if new_scc_count > original_scc_count - 1: # -1์ ์ ๊ฑฐ๋ ๋
ธ๋์ SCC
314 critical.append(remove_node)
315
316 return critical
317
318
319# =============================================================================
320# ํ
์คํธ
321# =============================================================================
322
323def main():
324 print("=" * 60)
325 print("๊ฐํ ์ฐ๊ฒฐ ์์ (SCC) ์์ ")
326 print("=" * 60)
327
328 # ๊ทธ๋ํ ๊ตฌ์ฑ
329 # 0 โ 1 โ 2
330 # โ โ โ
331 # 4 โ 3 โ 5 โ 6
332 # โ โ
333 # โโโโโโโโโ
334
335 n = 7
336 edges = [
337 (0, 1), (1, 2), (1, 3), (2, 5),
338 (3, 4), (4, 0), (3, 5), (5, 6), (6, 3)
339 ]
340
341 # 1. Kosaraju ์๊ณ ๋ฆฌ์ฆ
342 print("\n[1] Kosaraju ์๊ณ ๋ฆฌ์ฆ")
343 sccs = kosaraju_scc(n, edges)
344 print(f" ๊ฐ์ : {edges}")
345 print(f" SCC: {sccs}")
346
347 # 2. Tarjan ์๊ณ ๋ฆฌ์ฆ
348 print("\n[2] Tarjan ์๊ณ ๋ฆฌ์ฆ")
349 sccs = tarjan_scc(n, edges)
350 print(f" SCC: {sccs}")
351
352 # 3. SCC ์ถ์ฝ DAG
353 print("\n[3] SCC ์ถ์ฝ ๊ทธ๋ํ (DAG)")
354 sccs, scc_graph, node_to_scc = build_scc_dag(n, edges)
355 print(f" SCC: {sccs}")
356 print(f" ๋
ธ๋โSCC: {node_to_scc}")
357 print(f" SCC ๊ฐ์ : {dict(scc_graph)}")
358
359 # 4. 2-SAT ๋ฌธ์
360 print("\n[4] 2-SAT ๋ฌธ์ ")
361 # (x0 โจ x1) โง (ยฌx0 โจ x2) โง (ยฌx1 โจ ยฌx2)
362 sat = TwoSAT(3)
363 sat.add_clause(0, False, 1, False) # x0 โจ x1
364 sat.add_clause(0, True, 2, False) # ยฌx0 โจ x2
365 sat.add_clause(1, True, 2, True) # ยฌx1 โจ ยฌx2
366
367 solvable, assignment = sat.solve()
368 print(f" ์กฐ๊ฑด: (x0 โจ x1) โง (ยฌx0 โจ x2) โง (ยฌx1 โจ ยฌx2)")
369 print(f" ํด๊ฒฐ ๊ฐ๋ฅ: {solvable}")
370 if solvable:
371 print(f" ํด: x0={assignment[0]}, x1={assignment[1]}, x2={assignment[2]}")
372
373 # ๋ถ๊ฐ๋ฅํ 2-SAT
374 print("\n ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ:")
375 sat2 = TwoSAT(1)
376 sat2.add_clause(0, False, 0, False) # x0 โจ x0 = x0
377 sat2.add_clause(0, True, 0, True) # ยฌx0 โจ ยฌx0 = ยฌx0
378 solvable2, _ = sat2.solve()
379 print(f" ์กฐ๊ฑด: x0 โง ยฌx0")
380 print(f" ํด๊ฒฐ ๊ฐ๋ฅ: {solvable2}")
381
382 # 5. ํ๊ต ์ฐ๊ฒฐ
383 print("\n[5] ํ๊ต ์ฐ๊ฒฐ ๋ฌธ์ ")
384 school_roads = [(0, 1), (1, 2), (2, 0), (3, 4), (4, 3)]
385 min_roads = min_roads_to_connect(5, school_roads)
386 print(f" ๋๋ก: {school_roads}")
387 print(f" ์ถ๊ฐ ํ์ ๋๋ก: {min_roads}๊ฐ")
388
389 # 6. ์๊ณ ๋ฆฌ์ฆ ๋น๊ต
390 print("\n[6] Kosaraju vs Tarjan ๋น๊ต")
391 print(" | ํน์ฑ | Kosaraju | Tarjan |")
392 print(" |----------------|---------------|---------------|")
393 print(" | ์๊ฐ๋ณต์ก๋ | O(V + E) | O(V + E) |")
394 print(" | DFS ํ์ | 2๋ฒ | 1๋ฒ |")
395 print(" | ์ญ๊ทธ๋ํ ํ์ | ์ | ์๋์ค |")
396 print(" | ๊ตฌํ ๋์ด๋ | ์ฌ์ | ๋ณดํต |")
397
398 print("\n" + "=" * 60)
399
400
401if __name__ == "__main__":
402 main()