关于2-sat的建图方法及解决方案


2 - SAT:

一个事物具有两面性,并且与其它事物存在约束关系

 

 

注意在建图的时候 不仅可以是在每次给出的两个间建边,还可以与其他的点建边

-------------------------------------------------对于2-sat问题的描述-------------------------------------------------

给出一个序列,每个数是一个bool值,给出一些限制关系,得到最终的可行解的问题叫做适应性问题,也就是sat问题,2-sat问题就是给出的限制最多是两两元素之间的限制。

这种适应性问题的解决,同样是能够抽象为我们已知的图论模型的。

--------------------------------------------------2-sat问题的建图方法--------------------------------------------------

1.我们利用一条有向边<i,j>,来表示选i的情况下,一定要选j;

2.用i表示某个点是true,那么i'表示某个点是false

3.因为限制的两两之间的关系,所以我们可以通过逻辑关系来建边:

在一些确定的关系中,要以确定的关系建边,可以为任意值的不建,只建有限制的(例 poj 2296 ),这种类型 要注意列出所有情况

 还有 就是要明确i和~i是什么 例如poj2296 是正方形在点的上边 还是下边   hdu1814 是i去还是i^1去 而不是 i去还是不去

若图中存在有向边i->j,则表示若选了i必须选j

 

默认下面的x  y都为1 也就是选择~~

 

And 结果为1:建边 ~x->x, ~y->y (两个数都为1)  

 

And 结果为0:建边 y->~x , x->~y(两个数至少有一个为0)

 

OR  结果为1:建边 ~x->y , ~y->x(两个数至少有一个为1)

 

OR  结果为0:建边 x->~x , y->~y(两个数都为0)

 

XOR 结果为1:建边 x->~y , ~x->y , ~y->x , y -> ~x (两个数一个为0,一个为1)

 

XOR 结果为0:建边 x->y , ~x->~y , y->x, ~y->~x(两个数同为1或者同为0)

 

这么建图之后,会出现一个有向图,这个有向图会导致一个连通环,导致某个点一旦选取,那么这条链上的所有点都要被选中。如果我们找到一个强连通分量,那么这个强连通分量当中的点,如果选取必须全部选取,不选取的话一定是全部不选取,所以只要满足这个有向图中连通的点不会导致i和i'同时被选取,如果不存在矛盾,那么当前问题就是有解的。但是往往在求解过程中,我们要求的解会要求一些性质,所以提供以下几种解决方案。

------------------------------------------------2-sat问题的解决方案--------------------------------------------------------

1.求字典序最小的解的方法:

暴力dfs求解(复杂度O(N*M))

就是蓝书上的代码就是了  栈中的顺序即保证了字典序最小

 

2.判断当前的2-sa问题t是否有解

tarjan强连通缩点,加判断(复杂度O(N+M))

就是判断u --> v 是否在同一个连通分量中

具体讲解见 伍昱:《由对称性解2-SAT问题》

 for(int i = 0; i < 2 * n; i++)  
        if(!vis[i]) tarjan (i);  
  
    for(int i = 0; i < n; i++)  
        if(sccno[i << 1] == sccno[i << 1 | 1] )  
            return false;  
    return true;  

 

3.求出当前的2-sat问题的任意一组解

tarjan强连通缩点 + 反向建边 + 拓扑排序(染色) + 构建一组解(复杂度O(N+M))

选择一个没有被染色的点u 将其染成1 把所有与u点矛盾的点v和v的子孙染成2 不断重复操作 直到没有点可以染色而止


例题

Wedding

 POJ - 3648

n对夫妻去参加婚礼 

但有m对人有通奸关系,(也有可能同性),不能让新娘看到有通奸关系的两个人坐在一边,n对夫妻必须不能坐在一边

新娘带着超豪华的头饰,以至于她不能看到和她一遍的人,现在求一组和新娘坐在一边的人的解

 意思就是 有通奸关系的可以一边一个 ,也可以都坐在新娘的那边 (因为她看不到)

也就是至少有一个1

 

 

#include <iostream>
#include <cstdio>
#include <sstream>
#include <cstring>
#include <map>
#include <cctype>
#include <set>
#include <vector>
#include <stack>
#include <queue>
#include <algorithm>
#include <cmath>
#include <bitset>
#define rap(i, a, n) for(int i=a; i<=n; i++)
#define rep(i, a, n) for(int i=a; i<n; i++)
#define lap(i, a, n) for(int i=n; i>=a; i--)
#define lep(i, a, n) for(int i=n; i>a; i--)
#define rd(a) scanf("%d", &a)
#define rlld(a) scanf("%lld", &a)
#define rc(a) scanf("%c", &a)
#define rs(a) scanf("%s", a)
#define pd(a) printf("%d\n", a);
#define plld(a) printf("%lld\n", a);
#define pc(a) printf("%c\n", a);
#define ps(a) printf("%s\n", a);
#define MOD 2018
#define LL long long
#define ULL unsigned long long
#define Pair pair<int, int>
#define mem(a, b) memset(a, b, sizeof(a))
#define _  ios_base::sync_with_stdio(0),cin.tie(0)
//freopen("1.txt", "r", stdin);
using namespace std;
const int maxn = 10010, INF = 0x7fffffff, LL_INF = 0x7fffffffffffffff;
int n, m;
vector<int> G[maxn];
vector<int> f[maxn];
int vis[maxn], low[maxn], sccno[maxn], scc_clock, scc_cnt;
int head[maxn], cnt, in[maxn],col[maxn], cft[maxn];
stack<int> S;

void dfs(int u)
{
    low[u] = vis[u] = ++scc_clock;
    S.push(u);
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!vis[v])
        {
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if(!sccno[v])
            low[u] = min(low[u], vis[v]);
    }
    if(vis[u] == low[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x = S.top(); S.pop();
            sccno[x] = scc_cnt;
            if(x == u) break;
        }
    }
}

void bfs()
{
    queue<int> Q;
    for(int i = 1; i <= scc_cnt; i++)   //把入度为0的加入的队列中
        if(!in[i])
            Q.push(i);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        if(!col[u])     //染色
        {
            col[u] = 1;
            col[cft[u]] = 2;
        }
        for(int i = 0; i < f[u].size(); i++)
        {
            int v = f[u][i];
            in[v]--;        //删除与u相关的边
            if(!in[v])
                Q.push(v);
        }
    }
}

void init()
{
    for(int i = 0; i < maxn; i++) f[i].clear(), G[i].clear();
    while(!S.empty()) S.pop();
    mem(head, -1);
    mem(vis, 0);
    mem(sccno, 0);
    mem(in, 0);
    mem(col, 0);
    cnt = scc_clock = scc_cnt = 0;
}

int main()
{
    while(cin >> n >> m && n + m)
    {
        init();
        int x, y;
        char a, b;
        for(int i = 0; i < m; i++)
        {
            scanf("%d%c%d%c", &x, &a, &y, &b);
            if(a == 'w') x = 2 * x;
            else if(a == 'h') x = 2 * x + 1;
            if(b == 'w') y = 2 * y;
            else if(b == 'h') y  = 2 * y + 1;
            G[x^1].push_back(y);
            G[y^1].push_back(x);
        }
        G[0].push_back(1);
        for(int i = 0; i < 2 * n; i++)
        {
            if(!vis[i])
                dfs(i);
        }
        int flag = 0;
        for(int i = 0; i < n * 2; i+=2) //判断是否有解
        {
            int u = sccno[i], v = sccno[i + 1];
            if(u == v)
            {
                flag = 1;
                cout << "bad luck" << endl;
                break;
            }
            cft[u] = v;     //同时标记对立点
            cft[v] = u;
        }
        if(flag) continue;
        for(int i = 0; i < n * 2; i++)  //建反向边
        {
            for(int j = 0; j < G[i].size(); j++)
            {
                int u = sccno[i], v = sccno[G[i][j]];
                if(u != v)
                {
                    f[v].push_back(u);
                    in[u]++;
                }
            }
        }
        bfs();      //拓扑排序
        for(int i = 2; i < n * 2; i+=2)
        {
            if(i != 2) cout << " ";
            if(col[sccno[i]] == col[sccno[0]]) cout << i/2 << "w";
            else cout << i/2 << "h";
        }
        cout << endl;
    }


    return 0;
}

 

 

参考文献:

https://blog.csdn.net/qq_24451605/article/details/47126143

https://blog.csdn.net/u012915516/article/details/48442265

posted @ 2018-10-15 15:31  WTSRUVF  阅读(592)  评论(0编辑  收藏  举报