问题定义
SAT(satisfiability / 适定性问题):对于一个合取范式,求解使得该合取范式的值为1。
a and (b or c) and (d or e or f) and ...
2-SAT:该合取范式的每一个子式只包含两个变量。
(a or b) and (c or d) and (e or f) and ...
2-SAT算法
构造有向图G。构造方法:
合取范式中的每个变量衍生两个顶点:0和1(即:a=0, a = 1, b = 0, b = 1 ...)。
说明:为了方便起见,下文将a = 0的点简写为a0,a = 1的点简写为a1,以此类推。
合取范式中的每个子式衍生两条有向边:例如,由(a or b)得到a0 -> b1、b0 -> b1,意为若a为0则b必须为1,若b为0则a必须为1。
求解步骤1中得到的有向图的强连通分量。如果存在某变量x,由该变量衍生的两个顶点x0和x1位于同一强连通分量上,则问题无解;否则将位于同一强连通分量上的点缩为一个点,得到新的有向图G'。
将步骤2中得到的有向图的边反向,得到有向图G''。
将步骤3中得到的有向图的顶点G''置为“未着色”状态,按照拓扑顺序重复以下操作:
选择第一个未着色的顶点X,将X染成红色。
把所有与X矛盾的顶点Y及其子孙全部染成蓝色。(G''中某顶点X的矛盾点的定义:G中存在x0(或x1)属于X,x1(或x0)属于Y,则Y为X的矛盾点)
重复操作1和2,直到不存在未着色的点为止。
G''中被染成红色的点在图G中对应的顶点的集合,即为该2-SAT问题的一组解。
C++代码实现
以poj_3648为例:
https://github.com/HouJP/ACM_POJ/blob/master/poj_3648.cpp
/*************************************************************************
> File Name: poj_3648.cpp
> Author: HouJP
> Mail: peng_come_on@126.com
> Created Time: 一 8/11 22:59:04 2014
************************************************************************/
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#define N (30)
#define M (4000)
using namespace std;
int n, m;
vector<vector<int> > adj;
// tarjan
int dindex, stop, bcnt;
int dfn[2 * N + 10], low[2 * N + 10], belong[2 * N + 10], stap[2 * N + 10];
bool instack[2 * N + 10];
// dag
vector<vector<int> > dag;
int indegree[2 * N + 10];
// topsort
queue<int> q_topsort, q_blue;
char color[2 * N + 10];
void tarjan(int i) {
int j;
dfn[i] = low[i] = ++dindex;
instack[i] = true;
stap[++stop] = i;
for (int k = 0; k < adj[i].size(); ++k) {
j = adj[i][k];
if (!dfn[j]) {
tarjan(j);
if (low[j] < low[i]) low[i] = low[j];
}
else if (instack[j] && dfn[j] < low[i]) {
low[i] = dfn[j];
}
}
if (dfn[i] == low[i]) {
do {
j = stap[stop--];
instack[j] = false;
belong[j] = bcnt;
} while (j != i);
++bcnt;
}
}
void tarjan_solve() {
stop = bcnt = dindex = 0;
memset(dfn, 0, sizeof(dfn));
memset(instack, false, sizeof(instack));
for (int i = 0; i < 2 * n; ++i) {
if (!dfn[i]) tarjan(i);
}
}
bool is_solvable() {
for (int i = 0; i < 2 * n; i += 2) {
if (belong[i] == belong[i + 1]) return false;
}
return true;
}
void get_dag() {
dag.assign(bcnt, vector<int>());
memset(indegree, 0, sizeof(indegree));
for (int i = 0; i < 2 * n; ++i) {
for (int j = 0; j < adj[i].size(); ++j) {
if (belong[i] != belong[adj[i][j]]) {
dag[belong[adj[i][j]]].push_back(belong[i]);
++indegree[belong[i]];
}
}
}
}
void topsort() {
int now, color_now;
for (int i = 0; i < bcnt; ++i) {
if (0 == indegree[i]) {
q_topsort.push(i);
}
}
while (!q_topsort.empty()) {
now = q_topsort.front();
q_topsort.pop();
indegree[now] = -1;
for (int i = 0; i < dag[now].size(); ++i) {
--indegree[dag[now][i]];
if (0 == indegree[dag[now][i]]) q_topsort.push(dag[now][i]);
}
// painting
if (0 != color[now]) continue;
else {
color[now] = 'R';
for (int i = 0; i < 2 * n; ++i) {
if (now == belong[i]) {
q_blue.push(belong[i ^ 1]);
while (!q_blue.empty()) {
color_now = q_blue.front();
q_blue.pop();
if ('B' == color[color_now]) continue;
else {
color[color_now] = 'B';
for (int j = 0; j < dag[color_now].size(); ++j) {
q_blue.push(dag[color_now][j]);
}
}
}
break;
}
}
}
}
}
void print() {
for (int i = 1; i < n; ++i) {
if (1 != i) printf(" ");
if ('R' == color[belong[2 * i]]) {
printf("%dh", i);
} else {
printf("%dw", i);
}
}
printf("\n");
}
int main() {
freopen("input.dat", "r", stdin);
while (~scanf("%d %d", &n, &m) && (n || m)) {
int a, b;
char ca, cb;
adj.assign(2 * n, vector<int>());
// make adjacent matrix
for (int i = 0; i < m; ++i) {
scanf("%d%c%d%c", &a, &ca, &b, &cb);
if ('w' == ca) a = 2 * a;
else a = 2 * a + 1;
if ('w' == cb) b = 2 * b;
else b = 2 * b + 1;
if (a != (b ^ 1)) {
adj[a].push_back(b ^ 1);
adj[b].push_back(a ^ 1);
}
}
adj[0].push_back(1);
// tarjan
tarjan_solve();
if (false == is_solvable()) {
printf("bad luck\n");
continue;
}
// get DAG
get_dag();
// topsort
memset(color, 0, sizeof(color));
topsort();
// print
print();
}
}
小技巧
"^"的使用:
// 与0异或保持不变,与1异或即为取反
0^0 = 0; 0^1 = 1;
1^0 = 1; 1^1 = 0;
vector
// 将n个elem值拷贝给vector
vector.assign(n, elem)
相关知识
强连通分量
- 定义: 有向图强连通分量在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
- 求解: Tarjan算法、Korasaju算法
拓扑排序:
- 定义: 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
参考资料
- 由对称性解2-SAT问题, 伍昱
- 2-SAT 解法浅析, 赵爽