アーカイブ

BOJ 4196 - ドミノ

旧アーカイブから保存した技術記事: BOJ 4196 - ドミノ。

問題: BOJ 4196 — ドミノ · English · 한국어

各テストケースでは、有向グラフが与えられます。ドミノ u を倒すと、有向辺に沿って u から到達できるドミノがすべて倒れます。すべての頂点を倒すために最初に倒すドミノの最小数を求めます。

まず頂点を強連結成分(SCC)に分けます。同じSCC内ではどの頂点からもほかのすべての頂点に到達できるため、その成分のドミノを一つ倒せば成分全体が倒れます。各SCCを一つの頂点にまとめ、異なるSCC間の辺を残すと、縮約グラフ(SCC DAG)ができます。

縮約グラフで入次数が0のSCCには、必ず最初の一押しが必要です。他の成分からそこへ到達できないためです。逆に、入次数0のSCCを一つずつ倒せば十分です。有限DAGの任意の頂点は、ある始点(入次数0の頂点)から到達できます。各頂点から入ってくる辺をたどり続ければ、有限グラフなのでいずれ入次数0の頂点に着くからです。したがって答えは、入次数0のSCCの個数です。

実装では反復型のKosaraju法を使います。最初のDFSでは明示的なスタックで終了順を記録し、逆辺のグラフを終了順の逆順にたどってSCC IDを割り当てます。再帰呼び出しを使わないため、頂点が100,000個ある長い経路でも呼び出しスタックがあふれません。最後に辺を走査し、異なるSCCから辺が入る成分を記録します。各頂点と辺を定数回処理するので、時間計算量は O(V + E)、空間計算量も O(V + E) です。

Java

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.Arrays;

public class Main {
    private static final class FastScanner {
        private final BufferedInputStream input = new BufferedInputStream(System.in);
        private final byte[] buffer = new byte[1 << 16];
        private int position;
        private int limit;

        int nextInt() throws IOException {
            int c;
            do {
                c = read();
            } while (c <= ' ' && c != -1);

            int value = 0;
            while (c > ' ') {
                value = value * 10 + c - '0';
                c = read();
            }
            return value;
        }

        private int read() throws IOException {
            if (position == limit) {
                limit = input.read(buffer);
                position = 0;
                if (limit == -1) {
                    return -1;
                }
            }
            return buffer[position++];
        }
    }

    public static void main(String[] args) throws Exception {
        FastScanner input = new FastScanner();
        int testCases = input.nextInt();
        StringBuilder answer = new StringBuilder();

        while (testCases-- > 0) {
            int n = input.nextInt();
            int m = input.nextInt();
            int[] head = new int[n];
            int[] reverseHead = new int[n];
            Arrays.fill(head, -1);
            Arrays.fill(reverseHead, -1);
            int[] to = new int[m];
            int[] next = new int[m];
            int[] reverseTo = new int[m];
            int[] reverseNext = new int[m];

            for (int edge = 0; edge < m; edge++) {
                int from = input.nextInt() - 1;
                int destination = input.nextInt() - 1;
                to[edge] = destination;
                next[edge] = head[from];
                head[from] = edge;
                reverseTo[edge] = from;
                reverseNext[edge] = reverseHead[destination];
                reverseHead[destination] = edge;
            }

            boolean[] visited = new boolean[n];
            int[] order = new int[n];
            int orderSize = 0;
            int[] nodeStack = new int[n];
            int[] edgeStack = new int[n];

            for (int start = 0; start < n; start++) {
                if (visited[start]) {
                    continue;
                }
                int top = 0;
                nodeStack[0] = start;
                edgeStack[0] = head[start];
                visited[start] = true;

                while (top >= 0) {
                    int edge = edgeStack[top];
                    if (edge == -1) {
                        order[orderSize++] = nodeStack[top--];
                        continue;
                    }
                    edgeStack[top] = next[edge];
                    int neighbor = to[edge];
                    if (!visited[neighbor]) {
                        visited[neighbor] = true;
                        nodeStack[++top] = neighbor;
                        edgeStack[top] = head[neighbor];
                    }
                }
            }

            int[] component = new int[n];
            Arrays.fill(component, -1);
            int[] stack = new int[n];
            int componentCount = 0;
            for (int i = orderSize - 1; i >= 0; i--) {
                int start = order[i];
                if (component[start] != -1) {
                    continue;
                }
                int size = 0;
                stack[size++] = start;
                component[start] = componentCount;
                while (size > 0) {
                    int node = stack[--size];
                    for (int edge = reverseHead[node]; edge != -1; edge = reverseNext[edge]) {
                        int neighbor = reverseTo[edge];
                        if (component[neighbor] == -1) {
                            component[neighbor] = componentCount;
                            stack[size++] = neighbor;
                        }
                    }
                }
                componentCount++;
            }

            boolean[] hasIncoming = new boolean[componentCount];
            for (int from = 0; from < n; from++) {
                for (int edge = head[from]; edge != -1; edge = next[edge]) {
                    int sourceComponent = component[from];
                    int destinationComponent = component[to[edge]];
                    if (sourceComponent != destinationComponent) {
                        hasIncoming[destinationComponent] = true;
                    }
                }
            }

            int pushes = 0;
            for (boolean incoming : hasIncoming) {
                if (!incoming) {
                    pushes++;
                }
            }
            answer.append(pushes).append('\n');
        }

        System.out.print(answer);
    }
}