以下内容摘自《算法竞赛入门经典训练指南》:
2-SAT问题是这样的:有n个布尔变量xi,另有m个需要满足的条件,每个条件的形式都是“xi为真/假或者xj为真/假“
2-SAT的解法有多种不同的叙述方式,这里采用一种比较容易理解的,且效率也不错的方式。构造一张有向图,其中每个变量xi拆分成两个结点2i和2i + 1,分别表示xi为假和xi为真,最后要为每个变量选其中的一个结点进行标记,比如标记了2i,表示xi为假,标记了2i + 1表示xi为真了
对于“xi为假或者xj为假“这种条件,就连一条有向边,2i + 1->2j,表示如果标记了xi为真,那么xj就必须要为假,同理,还得另外一条有向边2j + 1-> 2i,表明如果xj为真了,xi必须为假,这样才能满足上述的条件
接下来考虑一下每个没有赋值的变量,设为xi。先假设他为假,然后标记结点2i,接着沿着有向边标记所有能标记的点,如果标记过程中发现某个变量对应的两个结点都被标记,则“xi为假“这个假设是不成立的,需要改成“xi为真“,然后重新标记。注意,这个算法没有回溯过程,如果当前考虑的变量不管赋值为真还是为假都会引起矛盾的话,那就可以证明整个2-SAT问题无解(即使调整以前赋值的其他变量也没用)
算法模版:
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int MAXNODE = 5010 * 2;
struct TwoSat{
int n, top;
vector<int> G[MAXNODE];
bool mark[MAXNODE];
int Stack[MAXNODE];
void init(int n) {
this->n = n;
for (int i = 0; i < 2 * n; i++)
G[i].clear();
memset(mark, 0, sizeof(mark));
}
//x = xval或者y = yval,这个表示或的关系
void AddClause(int x, int xval, int y, int yval) {
x = x * 2 + xval;
y = y * 2 + yval;
G[x ^ 1].push_back(y);
G[y ^ 1].push_back(x);
}
//x = xval的时候,y必须等于yval,这个表示且的关系
void AddLimit(int x, int xval, int y, int yval) {
x = x * 2 + xval;
y = y * 2 + yval;
G[x].push_back(y);
}
bool dfs(int u) {
//当前需要标记的结点是u,如果u ^ 1这个结点已经被标记过了,就表示两个结点都要被标记掉,明显冲突了
if (mark[u ^ 1]) return false;
if (mark[u]) return true;
mark[u] = true;
//模拟栈
Stack[++top] = u;
//和u相邻的点都进行标记,以判断是否出错
for (int i = 0; i < G[u].size(); i++)
if (!dfs(G[u][i])) return false;
return true;
}
bool solve() {
for (int i = 0; i < 2 * n; i += 2)
//如果在其他点的标志过程中,其中一个点被标记了,就无需在枚举了,因为在dfs的过程中,和其相连的点也被标记了,较少了重复枚举
if (!mark[i] && !mark[i ^ 1]) {
top = 0;
//假设xi为假是错误的,这时候就要进行回溯,将dfs过程中标记过的点还原,再判断对错
if (!dfs(i)) {
while (top) mark[Stack[top--]] = false;
if (!dfs(i ^ 1)) return false;
}
}
return true;
}
}Two;
int main() {
return 0;
}
习题的话,可以在本博客找,我有进行分类的