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


    融合增减:

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

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

    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
    ", a);
    #define plld(a) printf("%lld
    ", a);
    #define pc(a) printf("%c
    ", a);
    #define ps(a) printf("%s
    ", 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;
    }
    自己选择的路,跪着也要走完。朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。
  • 相关阅读:
    RadGrid Expand/Collapse on Row click
    AutoComplete Textbox with Additional Parameters From Database
    Combobox.Items中添加项Items
    JavaScript 处理字符串(操作字符串)
    用nettiers + svn + resharper + rad + ccNet开发前的准备工作
    Document.location.href和.replace的区别
    .net remoting的事务传播以及wcf分布式事务
    IDA反汇编/反编译静态分析iOS模拟器程序(三)函数表示与搜索函数
    [置顶] 一道有趣的逻辑题
    mini2440uboot移植基本操作指令
  • 原文地址:https://www.cnblogs.com/WTSRUVF/p/9791282.html
Copyright © 2020-2023  润新知