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 解法浅析, 赵爽

转载于:https://www.cnblogs.com/HouJP/p/3908497.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值