2-SAT问题

2-SAT问题

        现有一个由N个布尔值组成的序列A,给出一些限制关系,比如A[x]AND A[y]=0、A[x] OR A[y] OR A[z]=1等,要确定A[0..N-1]的值,使得其满足所有限制关系。这个称为SAT问题,特别的,若每种限制关系中最多只对两个元素进行限制,则称为2-SAT问题。

        由于在2-SAT问题中,最多只对两个元素进行限制,所以可能的限制关系共有11种:

        A[x]

        NOT A[x]   

        A[x] AND A[y]

        A[x] AND NOT A[y]

        A[x] OR A[y]

        A[x] OR NOT A[y]

        NOT (A[x] AND A[y])

        NOT (A[x] OR A[y])

        A[x] XOR A[y]

        NOT (A[x] XOR A[y])

        A[x] XOR NOT A[y]

        进一步,A[x] ANDA[y]相当于(A[x]) AND (A[y])(也就是可以拆分成A[x]与A[y]两个限制关系),NOT(A[x]OR A[y])相当于NOT A[x] AND NOT A[y](也就是可以拆分成NOT A[x]与NOT A[y]两个限制关系)。因此,可能的限制关系最多只有9种。

        在实际问题中,2-SAT问题在大多数时候表现成以下形式:有N对物品,每对物品中必须选取一个,也只能选取一个,并且它们之间存在某些限制关系(如某两个物品不能都选,某两个物品不能都不选,某两个物品必须且只能选一个,某个物品必选)等,这时,可以将每对物品当成一个布尔值(选取第一个物品相当于0,选取第二个相当于1),如果所有的限制关系最多只对两个物品进行限制,则它们都可以转化成9种基本限制关系,从而转化为2-SAT模型。

(引自:http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html)

         在程序实现中,我们把初始的n个物品变成2n个节点,然后从0开始编号到2*n-1号。其中原始第i个物品对应节点i*2和i*2+1。如果我们mark[i*2]节点,那么表示我们i节点设为假,如果我们mark[i*2+1]节点,那么我们i节点设为真。同一个节点只能mark一种结果(即对于原始i来说,我们只能mark[i*2]mark[i*2+1]其中之一)。

         然后加入存在i假或j假的论述,我们就引一条图中从2*i+1到2*j的边,再引一条2*j+1到2*i的边,表示如果i是真的,那么j肯定是假的(否则之前的结论不成立)。且如果j是真的,那么i肯定是假的(否则之前的结论也不成立)。

         如果存在i为真的论述,那么我们直接mark[i*2+1]即可。

         最终判断整个问题是否有解,就是做多次dfs来设置每个节点可能的值(真或假),看看是否所有可能取值情况都会冲突。如果不冲突,那么有解。(这里并不需要暴力枚举所有的可能,具体请看刘汝佳<<训练指南>>P323)

         注意:下面解题的过程中,比如我们要设定i为假,那么我们不是mark[i*2]=true,而是添加一条i*2+1->i*2的边,即只要i设为真了,那么就会使得导出矛盾。因为每个节点只有两种选择,所以上面添加边的思路更直观。反而每次都去转换成一条或语句更不直观。我基本上所有的题解都是以添加边的角度来做的。

         下面给出2-SAT的模板代码:

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=10000+10;
struct TwoSAT
{
    int n;//原始图的节点数(未翻倍)
    vector<int> G[maxn*2];//G[i]==j表示如果mark[i]=true,那么mark[j]也要=true
    bool mark[maxn*2];//标记
    int S[maxn*2],c;//S和c用来记录一次dfs遍历的所有节点编号

    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)条件
    //xval=0表示假,yval=1表示真
    void add_clause(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执行dfs遍历,途径的所有点都标记
    //如果不能标记,那么返回false
    bool dfs(int x)
    {
        if(mark[x^1]) return false;//这两句的位置不能调换
        if(mark[x]) return true;
        mark[x]=true;
        S[c++]=x;
        for(int i=0;i<G[x].size();i++)
            if(!dfs(G[x][i])) return false;
        return true;
    }

    //判断当前2-SAT问题是否有解
    bool solve()
    {
        for(int i=0;i<2*n;i+=2)
        if(!mark[i] && !mark[i+1])
        {
            c=0;
            if(!dfs(i))
            {
                while(c>0) mark[S[--c]]=false;
                if(!dfs(i+1)) return false;
            }
        }
        return true;
    }
};


2-SAT应用

POJ 3207 Ikki's Story IV - Panda's Trick(2-SAT):圆内外连线问题。解题报告!

POJ 3678 Katu Puzzle(2-SAT):与或非判断数值。解题报告!

POJ 3683 Priest John's Busiest Day(2-SAT输出方案):判断区间是否重叠。解题报告!

POJ 3648 Wedding(2-SAT):婚礼座位安排。解题报告!

POJ 2723 Get Luffy Out(2-SAT):钥匙开门问题。解题报告!

POJ 2749 Building roads(2-SAT):如何构建道路。解题报告!

POJ 2296 Map Labeler(2-SAT):放置正方形。解题报告!

HDU 3062 Party(2-SAT简单题):夫妻选择出席聚会。解题报告!

HDU 1814 Peaceful Commission(2-SAT:最小字典序):代表参加会议。解题报告!

HDU 3622 Bomb Game(2-SAT+二分):放置炸弹问题。解题报告!

HDU 3715 Go Deeper(2-SAT):程序递归深度问题。解题报告!

HDU 1824 Let's go home(2-SAT):队员去留问题。解题报告!

POJ 3905 Perfect Election(简单2-SAT):选举问题。解题报告!

HDU 4115 Eliminate the Conflict (2-SAT):出拳问题。解题报告!

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值