티스토리 뷰
2-SAT 문제는 다음과 같다.
(a or b) 꼴의 식 여러 개를 and 연산으로 연립한 것이다. 이때 각 a, b, c 변수는 true, false 를 값으로 하는 변수이다. 각 변수에는 not 을 붙일 수 있다.
이 문제는 변수들 간의 방향 그래프로 모델링 한 후, SCC를 사용하여 해를 구할 수 있다.
첫 번째로, 각 항의 (a or b)는 모두 true 가 되어야 한다.
이제 하나의 항만 생각해 보자.
(a or b) 가 성립하기 위해서는 a가 false이면 b가 true / b가 false이면 a가 true 여야 한다.
이를 명제로 표현하면 !a -> b , !b -> a 이다. 이 명제들이 모두 성립한다면 (a or b) 가 성립한다.
이제, 모든 항들을 2개의 명제로 바꾸었다. 항의 개수가 m 개라면, 주어진 2-SAT 문제는 2*m 개의 모든 명제들을 모두 만족시키는 변수 x1, x2, x3, x4, x5, ... 들의 값을 찾으면 된다.
다음 단계는 변수 x1, !x1, x2, !x2, x3, !x3, x4, !x4, x5, !x5, ... 의 상태를 하나의 노드로 모델링하는 것이다.
명제 !a -> b가 있다면 말 그대로 !a 와 b 사이를 간선으로 연결해 준다. 이제 이를 통해 완성된 그래프를 통하여 원래의 2-SAT 문제에 대한 답을 찾아주면 된다.
만약 명제 a -> b 가 성립하고, b -> c 가 성립한다면 a -> c 가 성립하는 것도 자명하다. 따라서, 만약 완성된 함의 그래프에서 u에서 v가 도달 가능하다면 u -> v 라는 명제가 성립한다고 생각해도 좋다.
이러한 도달 가능성의 느낌으로 생각해보면 2-SAT 문제를 푸는데 함의 그래프에서의 SCC를 생각해 볼 수 있다.
만약 하나의 SCC안에 x와 !x가 모두 들어 있다고 생각해 보자. SCC의 정의에 따라 x -> !x, !x -> x 의 두 명제가 모두 성립해야 한다. 이는 모순이고, 이러할 경우에 답이 존재하지 않음을 알 수 있다.
이제 SCC를 구해서, x와 !x의 위치를 생각해 보자.
만약 x -> !x 의 경로가 존재한다 생각하자. 이때는 x가 false 면 명제가 성립한다.
같은 논리로 !x -> x 의 경로가 존재하면 x가 true 일때 명제가 성립한다.
만약 x와 !x 사이에 간선이 없다면 ? x가 뭐가 됐던 성립한다.
이를 간단하게 구현하는 방법은 위상정렬된 SCC에서의 번호를 생각하는 것이다. (코사라주는 순서대로, 타잔은 역순)
시간 복잡도 : $O(N+M)$
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int MAXN = 2e4;
int n, m;
vector<int> adj[MAXN+10], revadj[MAXN+10];
bool vis1[MAXN+10];
vector<int> order;
void dfs1(int now)
{
vis1[now]=true;
for(int nxt : adj[now]) if(!vis1[nxt]) dfs1(nxt);
order.push_back(now);
}
int scc[MAXN+10], scnt=1;
void dfs2(int now)
{
scc[now]=scnt;
for(int nxt : revadj[now]) if(scc[nxt]==0) dfs2(nxt);
}
int ans[MAXN+10];
int main()
{
int i, j;
scanf("%d%d", &n, &m);
for(i=1; i<=m; i++)
{
int a, b, na, nb;
scanf("%d%d", &a, &b);
if(a<0) a=n-a, na=a-n;
else a=a, na=a+n;
if(b<0) b=n-b, nb=b-n;
else b=b, nb=b+n;
adj[na].push_back(b); revadj[b].push_back(na);
adj[nb].push_back(a); revadj[a].push_back(nb);
//printf("%d %d / %d %d\n", na, b, nb, a);
}
for(i=1; i<=2*n; i++) if(!vis1[i]) dfs1(i);
reverse(order.begin(), order.end());
for(i=0; i<2*n; i++)
{
if(scc[order[i]]==0)
{
dfs2(order[i]);
scnt++;
}
}
for(i=1; i<=n; i++)
{
if(scc[i]==scc[i+n])
{
return !printf("0");
}
}
//for(i=1; i<=n; i++) printf("!%d %d\n", scc[i], scc[n+i]);
for(i=1; i<=n; i++)
{
if(scc[i]<scc[n+i]) ans[i]=0;
else ans[i]=1;
}
printf("1\n");
for(i=1; i<=n; i++) printf("%d ", ans[i]);
}
'알고리즘 & 자료구조 정리' 카테고리의 다른 글
LCA (Lowest Common Ancestor) (0) | 2019.07.03 |
---|---|
강한 결합 요소 (Strongly Connected Component) (0) | 2019.06.09 |
위상정렬 (Topological Sort) (0) | 2019.05.17 |
- Total
- Today
- Yesterday
- Shortest path
- graph
- Interactive
- Divide & Conquer
- Parametric Search
- HLD
- offline
- Greedy
- Segment Tree
- APIO
- stack
- Lazy Propagation
- Sqrt Decomposition
- Centroid Decomposition
- BOJ
- ioi
- ⭐
- Codeforces
- Line sweeping
- CHT
- convex hull
- DP
- Union Find
- DFS
- Sparse Table
- Fenwick Tree
- tree
- Floyd-Warshall
- Merge Sort
- Persistent Segment Tree
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |