아카이브

BOJ 11281 - 2-SAT - 4

이전 아카이브에서 보존한 기술 글: BOJ 11281 - 2-SAT - 4.

문제: BOJ 11281 — 2-SAT - 4 · English · 日本語

반복형 코사라주 알고리즘을 이용한 2-SAT

입력 절 (a ∨ b)는 함의 ¬a → b와 ¬b → a로 바꿀 수 있습니다. 부호가 있는 각 리터럴을 정점으로 표현합니다. 양수 리터럴 x의 인덱스는 x - 1, 음수 리터럴 ¬x의 인덱스는 N + x - 1입니다. 리터럴의 부정은 인덱스 index ^ N으로 구할 수 있습니다. 각 함의 간선을 그래프와 역방향 그래프에 모두 저장합니다.

변수와 그 부정이 같은 강한 연결 요소에 속하면 식은 만족 불가능합니다. 이 경우 한쪽 리터럴에서 다른 쪽으로, 다시 되돌아가는 경로가 있어 두 리터럴이 서로를 강제하고 동시에 참이어야 하기 때문입니다. 그렇지 않으면 코사라주 알고리즘으로 강한 연결 요소를 구합니다. 첫 번째 탐색은 원래 그래프에서 정점의 종료 순서를 기록하고, 두 번째 탐색은 종료 순서의 역순으로 역방향 그래프를 탐색합니다. 두 탐색 모두 명시적인 스택을 사용하므로 긴 함의 경로가 있어도 Java 호출 스택이 넘치지 않습니다.

두 번째 탐색에서 요소를 발견하는 순서대로 번호를 붙이면, 원래 함의 그래프에서 서로 다른 요소 사이의 모든 간선은 작은 번호에서 큰 번호로 향합니다. 따라서 x의 요소 번호가 ¬x의 번호보다 클 때 x를 참으로 정합니다. 이는 이 구현의 번호 부여 방식에 맞춘 역위상 정렬 순서의 값 할당 규칙입니다. 탐색 순서나 번호 규칙을 바꾸면 비교 방향도 바뀌어야 합니다.

첫 번째 탐색에서는 정점마다 다음에 확인할 간선의 인덱스를 저장해 재귀 DFS를 반복문으로 모사합니다. 정점의 모든 나가는 간선을 처리한 뒤에만 종료 순서에 추가합니다. 두 번째 탐색은 스택에 넣을 때 방문 표시를 하여 중복 삽입을 막습니다. 각 정점과 함의 간선을 상수 번만 처리하므로 시간과 공간 복잡도는 모두 O(N + M)입니다.

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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

        private int read() throws IOException {
            if (pointer == length) {
                length = in.read(buffer);
                pointer = 0;
                if (length == -1) {
                    return -1;
                }
            }
            return buffer[pointer++];
        }

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

            int sign = 1;
            if (c == '-') {
                sign = -1;
                c = read();
            }

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

    private static int literalIndex(int literal, int n) {
        return literal > 0 ? literal - 1 : n - literal - 1;
    }

    public static void main(String[] args) throws Exception {
        FastScanner input = new FastScanner();
        int n = input.nextInt();
        int m = input.nextInt();
        int vertexCount = 2 * n;

        List<Integer>[] graph = new ArrayList[vertexCount];
        List<Integer>[] reverseGraph = new ArrayList[vertexCount];
        for (int vertex = 0; vertex < vertexCount; vertex++) {
            graph[vertex] = new ArrayList<>();
            reverseGraph[vertex] = new ArrayList<>();
        }

        for (int clause = 0; clause < m; clause++) {
            int a = input.nextInt();
            int b = input.nextInt();
            int notA = literalIndex(-a, n);
            int indexA = literalIndex(a, n);
            int notB = literalIndex(-b, n);
            int indexB = literalIndex(b, n);

            graph[notA].add(indexB);
            reverseGraph[indexB].add(notA);
            graph[notB].add(indexA);
            reverseGraph[indexA].add(notB);
        }

        boolean[] visited = new boolean[vertexCount];
        int[] nextEdge = new int[vertexCount];
        int[] stack = new int[vertexCount];
        int[] order = new int[vertexCount];
        int orderSize = 0;

        for (int start = 0; start < vertexCount; start++) {
            if (visited[start]) {
                continue;
            }
            int top = 0;
            stack[top++] = start;
            visited[start] = true;

            while (top > 0) {
                int vertex = stack[top - 1];
                if (nextEdge[vertex] < graph[vertex].size()) {
                    int next = graph[vertex].get(nextEdge[vertex]++);
                    if (!visited[next]) {
                        visited[next] = true;
                        stack[top++] = next;
                    }
                } else {
                    order[orderSize++] = vertex;
                    top--;
                }
            }
        }

        int[] component = new int[vertexCount];
        int componentCount = 0;
        for (int index = orderSize - 1; index >= 0; index--) {
            int start = order[index];
            if (component[start] != 0) {
                continue;
            }
            int top = 0;
            stack[top++] = start;
            component[start] = ++componentCount;

            while (top > 0) {
                int vertex = stack[--top];
                for (int next : reverseGraph[vertex]) {
                    if (component[next] == 0) {
                        component[next] = componentCount;
                        stack[top++] = next;
                    }
                }
            }
        }

        for (int variable = 0; variable < n; variable++) {
            if (component[variable] == component[variable + n]) {
                System.out.println(0);
                return;
            }
        }

        StringBuilder output = new StringBuilder("1\n");
        for (int variable = 0; variable < n; variable++) {
            output.append(component[variable] > component[variable + n] ? '1' : '0');
            if (variable + 1 < n) {
                output.append(' ');
            }
        }
        output.append('\n');
        System.out.print(output);
    }
}