2-SAT 문제는 N개의 불리언 변수 x1,x2,…,xN가 있을 때, 주어진 2-CNF(Conjunctive Normal Form) 식을 참으로 만드는 변수의 값을 찾는 문제이다. 2-CNF 식은 여러 개의 절(clause)의 논리곱(∧)으로 구성되며, 각 절은 두 개의 리터럴(변수 또는 그 부정)의 논리합(∨)으로 이루어진다.
예를 들어, 다음과 같은 식을 생각해보자:
(¬x1∨x2)∧(¬x2∨x3)∧(x1∨x3)∧(x3∨x2)
여기서 ¬는 NOT을, ∨는 OR을, ∧는 AND를 의미한다. 이 식을 참으로 만들기 위해 각 변수에 적절한 값을 할당해야 한다.
문제는 변수의 개수 N과 절의 개수 M, 그리고 각 절에 포함된 변수들이 주어졌을 때, 전체 식을 참으로 만들 수 있는지 판단하고, 가능하다면 변수들의 값을 구하는 것이다.
예를 들어, N=3, M=4인 경우 위의 식을 참으로 만들기 위해 x1=False, x2=False, x3=True로 설정할 수 있다. 하지만 N=1, M=2, f=(x1∨x1)∧(¬x1∨¬x1)인 경우에는 x1에 어떤 값을 할당하더라도 식을 참으로 만들 수 없다.
접근 방식
이 문제는 2-SAT 문제로, 선형 시간 내에 해결할 수 있다. 주요 아이디어는 **임플리케이션 그래프(Implication Graph)**를 구성하고, **강한 연결 요소(Strongly Connected Components, SCC)**를 찾는 것이다.
임플리케이션 그래프 구성:
각 변수 xi와 그 부정 ¬xi를 노드로 표현한다.
각 절 (A∨B)는 두 개의 임플리케이션으로 변환된다:
¬A→B
¬B→A
이로써 그래프의 간선이 정의된다.
강한 연결 요소 찾기:
그래프에서 SCC를 찾기 위해 Kosaraju’s Algorithm을 사용한다.
만약 어떤 변수 xi와 그 부정 ¬xi가 같은 SCC에 속한다면, 모순이 발생하므로 식을 참으로 만들 수 없다.
#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;constintMAX_N=10000;intN,M;vector<int>adj[MAX_N*2];// 임플리케이션 그래프
vector<int>adj_rev[MAX_N*2];// 역 그래프
vector<int>order;// 노드 방문 순서
intscc_id[MAX_N*2];// 각 노드의 SCC ID
boolvisited[MAX_N*2];// 변수 번호를 인덱스로 변환
intvar(intx){returnx>0?(x-1)*2:(-x-1)*2+1;}// 1차 DFS: 노드 방문 순서 기록
voiddfs1(intu){visited[u]=true;for(intv:adj[u]){if(!visited[v])dfs1(v);}order.push_back(u);}// 2차 DFS: SCC 구성
voiddfs2(intu,intid){scc_id[u]=id;for(intv:adj_rev[u]){if(scc_id[v]==-1)dfs2(v,id);}}intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);cin>>N>>M;// 그래프 구성
for(inti=0;i<M;++i){inta,b;cin>>a>>b;intu=var(a);intv=var(b);// \lnot a -> b
adj[u^1].push_back(v);adj_rev[v].push_back(u^1);// \lnot b -> a
adj[v^1].push_back(u);adj_rev[u].push_back(v^1);}// 1차 DFS 수행
for(inti=0;i<N*2;++i){if(!visited[i])dfs1(i);}// SCC 초기화
fill(scc_id,scc_id+N*2,-1);// 2차 DFS 수행
intid=0;for(inti=N*2-1;i>=0;--i){intu=order[i];if(scc_id[u]==-1)dfs2(u,id++);}// 모순 검사
for(inti=0;i<N;++i){if(scc_id[i*2]==scc_id[i*2+1]){cout<<0<<'\n';return0;}}// 변수 값 결정
vector<int>result(N);for(inti=0;i<N;++i){result[i]=scc_id[i*2]>scc_id[i*2+1]?1:0;}// 결과 출력
cout<<1<<'\n';for(inti=0;i<N;++i){cout<<result[i]<<' ';}cout<<'\n';return0;}
Python의 재귀 한도를 늘려야 하므로 sys.setrecursionlimit을 설정한다.
모순 검사 및 결과 결정:
변수와 그 부정의 SCC ID를 비교하여 모순 여부를 판단한다.
변수 값은 SCC ID를 기반으로 결정한다.
결과 출력:
식을 만족할 수 있다면 1을 출력하고, 변수 값을 출력한다.
결론
이번 문제는 2-SAT 알고리즘의 전형적인 적용 예시였다. 임플리케이션 그래프와 SCC를 활용하여 효율적으로 해결할 수 있었다. 이러한 문제를 통해 그래프 이론과 논리 회로의 접점을 이해할 수 있었다. 추가적인 최적화 방안으로는 메모리 사용을 줄이기 위해 인접 리스트를 효율적으로 관리하는 방법 등이 있을 것이다.