17_scc.py

Download
python 403 lines 12.1 KB
  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()