算法基础 - 2-sat问题

来源:互联网 发布:蕲春农村淘宝 编辑:程序博客网 时间:2024/05/18 02:45

2-SAT问题

这个问题其实我们平时早就遇到过,只是从来没有认真的去思考这个问题的解法。这个在我们理解的时候,一般都是按照一个点开始,可以就继续,不可以就停止。

例子问题

例如我们有10个球,每个球都是编号好的,从:1 - 10这10个球,我们要从中拿出5个球,有以下要求:
并且选了1就不能选择3号球,选择了2号就不能选择4号球,选择了5号就不能选择6号。
但是这里面有个规则:{1, 2}不能同时存在。同理{3, 4}, {5 , 6}也不能存在。

这个问题很明了了,怎么解决呢?

最简单的就是枚举,首先选择一个球,看是否有规则,没有就选择下一个,有规则就按照规则选,不可以了,就回溯,一直到发现一个可以的情况,或者所有都不可以的时候,就有答案了。

可是这个方法是在太耗费时间了。更简单的办法,也就是利用图论的只是来解决。

下面是思想:

首先我们看第一个规则,1 3不能共存,那么这个时候可以得知,因为3 4必须选择一个,那么假如我们选择了1,就必须选择4了,这样我们就知道了一个边,就是1 -> 4这个边的意义就是,选择了1就必须选择4。那么假如我们选择的是3呢?那么另外一个就必须选择2。这个边就是3 -> 2了。依次类推,我们可以得到下面的图:

有向图

这样我们就得到了一个有向图,这个有向图的每条边都代表了选择了一个点,那么同时也要选择它指向的点。如果这个点是孤立的点,例如图中的7 8,那么就随便选择。那么怎么才算无解呢?

两个不相容的点,处在同一个环内,或者更通俗的说是,强连通图内。,就是无解的。

解法

上面通过例子已经讲解了如何解决这个问题:

  1. 构造有向图,这个有向图是一个表示了选择了一个点,一定要选择它能够到达的点的图。
  2. 对这个图求强连通图。
  3. 假如有两个点处在同一个强连通图内,这两个点是必须只能选择一个的一组内的点,就不可以。
  4. 否则可以

求强连通图的办法是Tarjan算法:传送门: Tarjan算法这是我之前博客里写的。

代码实现

下面这个代码是解决一个问题:

现在有一堆开关,1 2 3 4 5 ... n这么多,每个开关只有开关两个状态,现在要求,某些开关,不能同时开,或者不能同时关。

求解是否有这么一个状态,满足所有要求。

代码:

#include <iostream>#include <cstring>#include <string>#include <vector>#include <stack>using namespace std;#ifndef MAXNODE#define MAXNODE 20005#endifint flag[MAXNODE] = {0};int dfn[MAXNODE] = {0};int low[MAXNODE] = {0};int tpArr[MAXNODE] = {0};int tpNum = 0;int _index = 0;vector<int> graph[MAXNODE];stack<int> st;void tarjan(int n){    st.push(n);    flag[n] = 1;    dfn[n] = ++_index;    low[n] = _index;    for (int i = 0; i < graph[n].size(); i++) {        int cur = graph[n][i];        if (dfn[cur] == 0) {            tarjan(cur);            low[n] = min(low[n], low[cur]);        }else{            if (flag[cur] == 1) {                low[n] = min(low[n], dfn[cur]);            }        }    }    if (dfn[n] == low[n]) {        tpNum++;        do{            n = st.top();            st.pop();            flag[n] = 0;            tpArr[n] = tpNum;        }while(dfn[n] != low[n]);    }}void addNode(int n1, int n2){    if ((n1&1) > 0) {        graph[n1].push_back(n2+1);        graph[n2].push_back(n1+1);    }else{        graph[n1].push_back(n2-1);        graph[n2].push_back(n1-1);    }}void transAddNode(int a, int b, int l){    if (l == 1) {        addNode(a*2, b*2);    }else{        addNode(a*2-1, b*2-1);    }}bool judge(int N){    for (int i = 1; i <= N; i++) {        if (tpArr[2*i-1] == tpArr[2*i]) {            return false;        }    }    return true;}int main(){    int M;    cin>>M; //测试组数    while (M--) {        int N,T;        cin>>N>>T; //N是开关个数,T是规则个数        int a,b,c;        for (int i = 0; i < T; i++) {            cin>>a>>b>>c; //输入a b c分别代表开关a,开关b,已经开关状态c只有(0/1),表示开关a,b不能同时处于状态c            transAddNode(a, b, c);        }        for (int i = 1; i <= 2*N; i++) {            if (dfn[i] == 0) {                tarjan(i);            }        }        if(judge(N)){            cout<<"Yes"<<endl;        }else{            cout<<"No"<<endl;        }        for (int i = 1; i <= 2*N;i++) {            flag[i] = 0;            dfn[i] = 0;            low[i] = 0;            graph[i].clear();        }        _index = 0;    }    return 0;}
0 0
原创粉丝点击