본문 바로가기

Study

2-SAT 구하기

개요

2-SAT는 N개의 논리값을 가지는 변수를 사용하여 논리곱 정규형으로 표현했을 때, 각각의 절에 변수가 2개 존재하는 논리식을 의미합니다. 2-SAT는 기본적으로 아래와 같은 형식을 가지고 있습니다.

 

f = (~X1 ∨ X2) ∧ (~X2 ∨ X3) ∧ (X1 ∨ X3) ∧ (X3 ∨ X2)

 

이렇게 논리식 f를 참(true)로 만족하게 만드는 X1 ~ X3까지의 값을 구하는 것이 2-SAT 문제의 핵심입니다. 그럼 2-SAT의 값은 어떻게 구할 수 있을까요? 물론 X1 ~ X3까지 참(true)과 거짓(false)을 하나씩 넣어보는 브루트포스 방식으로 풀 수 있습니다. 그리고 2-SAT를 제외한 대부분의 CNF 문제(Ex. 3-SAT, 5-SAT)들은 NP-Hard문제로 브루트포스 방식으로 해결해야 합니다. 하지만 2-SAT는 2개의 변수만이 있다는 특수성 때문에 좀 더 빠르게 구할 수 있습니다. 그리고 그 때, SCC를 활용하게 됩니다.

 

방법

우선, 각각의 절을 분석해보아야 합니다. 논리식 f에서 가장 앞에 있는 (~X1 ∨ X2)을 예시로 살펴봅시다. (~X1 ∨ X2)가 참(true)를 만족하기 위해서는 다음과 같이 생각해볼 수 있습니다. 참고로, 논리식 f가 참(true)가 되기 위해서는 모든 절의 값이 참(true)이어야 합니다.

 

(1) 만약 ~X1가 거짓이라면, X2는 참이어야 한다. [ ~(~X1) >> X2 ]

(2) 만약 X2가 거짓이라면, ~X1는 참이어야 한다. [ ~X2 >> (~X1) ]

 

이렇게 2개의 명제가 만들어지며, 우리는 이 명제를 활용하여 그래프를 그려볼 수 있습니다. 그리고 이 그래프는 예상했겠지만 대칭을 이루게 됩니다. 이렇게 모든 절에 대해 명제를 2개씩 만들어보고, 이를 그래프로 표현하면 하나의 그래프를 만들 수 있습니다. 그리고 이 그래프를 분석하여 2-SAT를 구하면 됩니다.

 

우선, 논리식 f를 절대 참(true)으로 만들 수 없는 경우를 생각해봅시다. 이는 1개 이상의 명제에서 동일한 변수에 대해 참(true)와 거짓(false)이 연결되었다는 걸 의미합니다. 이를 그래프에 도입하면 Xn와 ~Xn이 서로 연결되었다는 걸 의미하며, 우리는 이를 그래프의 SCC를 구해보면 쉽게 파악할 수 있습니다. 그리고 SCC는 어떤 알고리즘(Kosaraju / Tajan)을 사용하든 동일한 시간 복잡도를 가지고 구할 수 있습니다.

 

만약, 직접 각각의 변수에 해당하는 값을 구해야 한다면 SCC가 만들어진 순서를 활용할 수 있습니다. SCC가 만들어진 순서는 곧 우리가 만든 논리식을 그래프로 표현했을 때, 위상 정렬되는 순서라고 할 수 있습니다. 그리고 위상 정렬되는 순서에 따라 나온 값을 거짓(false)으로 설정하면 우리가 원하는 결괏값을 얻을 수 있습니다. 아래 소스코드는 백준 11281번(2-SAT-4)번 문항(https://www.acmicpc.net/problem/11281)의 해결 소스코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <stdio.h>
#include <string.h>
#include <stack>
#include <vector>
#include <utility>
#include <algorithm>
#define NMAX 20010
using namespace std;
typedef pair<intint> PAIR;
 
int N, M, x, y;
vector< int > adj[NMAX], Radj[NMAX];
 
int top, sccIdx, f;
int visited[NMAX], sccNum[NMAX], ans[NMAX/2];
 
stack< int > st;
vector< PAIR > sccSort;
 
 
void dfs(int idx) {
    visited[idx] = 1;
    for(int nxt:adj[idx]) {
        if(!visited[nxt]) dfs(nxt);
    }
    
    st.push(idx);
}
 
void Rdfs(int idx) {
    visited[idx] = 1;
    for(int nxt:Radj[idx]) {
        if(!visited[nxt]) Rdfs(nxt);
    }
    
    sccNum[idx] = sccIdx;
    
    // 결괏값을 구하기 위해 sccIdx 순서로 저장
    sccSort.push_back({idx, sccIdx});
}
 
// Kosaraju's Algorithm
void getScc() {
    // 첫 번째 탐색
    for(int i=1*2;i<=N*2+1;i++) {
        if(!visited[i]) dfs(i);
    }
    
    // 두 번째 탐색 >> 역간선
    memset(visited, 0sizeof(visited));
    while(!st.empty()) {
        top = st.top();
        st.pop();
        
        if(!visited[top]) {
            sccIdx++;
            
            // top과 동일한 SCC에 해당되는 정점 찾기
            Rdfs(top);
        }
        
    }
}
 
int main() {
    // 입력
    scanf("%d %d"&N, &M);
    for(int i=1;i<=M;i++) {
        scanf("%d %d"&x, &y);
        
        // true: idx*2 / false: idx*2+1
        x = x>0 ? x*2:(-x)*2+1;
        y = y>0 ? y*2:(-y)*2+1;
        
        // ~x > y
        if(x%2==0) {
            adj[x+1].push_back(y);
            Radj[y].push_back(x+1);
        }
        else {
            adj[x-1].push_back(y);
            Radj[y].push_back(x-1);
        }
        
        // ~y > x
        if(y%2==0) {
            adj[y+1].push_back(x);
            Radj[x].push_back(y+1);
        }
        else {
            adj[y-1].push_back(x);
            Radj[x].push_back(y-1);
        }
    }
    
    // SCC 구하기
    getScc();
    
    // 2-SAT를 구할 수 있는지 확인
    f = 1;
    for(int i=1;i<=N and f;i++) {
        if(sccNum[i*2== sccNum[i*2+1]) f = 0;
    }
    
    if(!f) printf("0");
    else {
        printf("1\n");
        
        // SCC 순서대로 정렬하기 >> 낮을 수록 DAG에서 앞에 위치함
        // p > q에서 p가 false이면
        memset(ans, -1sizeof(ans));
        for(PAIR p:sccSort) {
            int idx = p.first;
            
            // 현재 값을 거짓으로 설정
            if(ans[idx/2== -1) ans[idx/2= idx%2;
        }
        
        // 출력
        for(int i=1;i<=N;i++printf("%d ", ans[i]);
        
    }
}
 
cs

 

 

이번에는 타잔 알고리즘을 활용하여 2-SAT-3(https://www.acmicpc.net/problem/11280)을 해결한 소스코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdio.h>
#include <stack>
#include <vector>
#include <algorithm>
#define NMAX 10010
using namespace std;
 
int N, M, a, b;
vector< int > graph[NMAX*2];
 
int vi, top, scci, f;
int visited[NMAX*2], scc[NMAX*2];
stack< int > st;
 
int getSCC(int cur) {
    int ret;
 
    ret = visited[cur] = vi++;
    st.push(cur);
 
    for(int nxt:graph[cur]) {
        if(!visited[nxt]) ret = min( ret, getSCC(nxt) );
        else if(!scc[nxt]) ret = min( ret, visited[nxt] );
    }
 
    if(ret == visited[cur]) {
        scci++;
 
        while(top != cur) {
            top = st.top();
            st.pop();
 
            scc[top] = scci;
        }
    }
 
    return ret;
}
 
int main() {
    scanf("%d %d"&N, &M);
    // 입력
    for(int i=1;i<=M;i++) {
        scanf("%d %d"&a, &b);
 
        a = a>0 ? a*2:(-a)*2+1;
        b = b>0 ? b*2:(-b)*2+1;
 
        // a를 만족하지 않는다면 b를 항상 만족하기
        if(a%2 == 0) graph[a+1].push_back(b);
        else graph[a-1].push_back(b);
 
        // b를 만족하지 않는다면 a를 항상 만족하기
        if(b%2 == 0) graph[b+1].push_back(a);
        else graph[b-1].push_back(a);
    }
 
    // SCC 구하기(Tajan)
    vi = 1;
    for(int i=2;i<=N*2+1;i++) {
        if(!scc[i]) getSCC(i);
    }
 
    // 2-SAT 만족 여부 확인하기
    f = 1;
    for(int i=1;i<=N and f;i++) {
        if(scc[i*2== scc[i*2+1]) f = 0;
    }
 
    // 출력
    printf("%d\n", f);
 
}
cs

'Study' 카테고리의 다른 글

KMP 알고리즘  (0) 2022.08.13
단절점과 단절선  (0) 2022.07.28
SCC 구하기  (0) 2022.07.26
람다식을 활용한 중첩 반복문 탈출( BOJ 20410 )  (0) 2022.07.11
모듈로 곱셈 역원_팩토리얼 계산( BOJ 13977 )  (0) 2022.07.11