2-SAT详解

以下内容摘自《算法竞赛入门经典训练指南》:

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 
#include 
#include 
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;
}

习题的话,可以在本博客找,我有进行分类的

你可能感兴趣的:(ACM-图论-2-SAT)