2-SAT问题

2-SAT问题


问题定义

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算法

  1. 构造有向图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。

  2. 求解步骤1中得到的有向图的强连通分量。如果存在某变量x,由该变量衍生的两个顶点x0和x1位于同一强连通分量上,则问题无解;否则将位于同一强连通分量上的点缩为一个点,得到新的有向图G'。

  3. 将步骤2中得到的有向图的边反向,得到有向图G''。

  4. 将步骤3中得到的有向图的顶点G''置为“未着色”状态,按照拓扑顺序重复以下操作:

    1. 选择第一个未着色的顶点X,将X染成红色。

    2. 把所有与X矛盾的顶点Y及其子孙全部染成蓝色。(G''中某顶点X的矛盾点的定义:G中存在x0(或x1)属于X,x1(或x0)属于Y,则Y为X的矛盾点)

    3. 重复操作1和2,直到不存在未着色的点为止。

  5. 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 解法浅析, 赵爽
posted @ 2014-08-12 21:09  HouJP  阅读(1154)  评论(0)    收藏  举报