2-SAT(随意写点)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wu_tongtong/article/details/78008148

今年noi考了一道2-SAT裸题,害怕今年省选会出到,只能填坑

SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT。
当k>2时,k-SAT是NP完全的。因此一般讨论的是k=2的情况,即2-SAT问题。
2-SAT,简单的说就是给出n个集合,每个集合有两个元素,
已知若干个 < a,b >,表示a与b矛盾(其中a与b属于不同的集合)。
然后从每个集合选择一个元素,一共选n个两两不矛盾的元素。
显然可能有多种选择方案,一般题中只需要求出一种即可。

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]

注意:
这里的OR是指两个条件至少有一个是正确的
比如x1和x2一共有三种组合满足“x1为真或x2为假”:
x1=1,x2=1
x1=1,x2=0
x1=0,x2=0

2-SAT的解决方法有很多,
由于博主比较蒟,所以就选择一种简单易懂的介绍一下:

算法

构造一个有向图G,每个变量xi拆成两个点2i和2i+1
分别表示xi为假,xi为真
那么对于“xi为真或xj为假”这样的条件
我们就需要连接两条边
2*i —>2*j(表示如果i为假,那么j必须是假)
2*j+1—>2*i+1(表示如果j为真,那么i必须是真)
这就有点像推导的过程
实际上每一个限制条件都会对应两条“对称”的边

接下来逐考虑每个没有赋值的变量,设为x
我们先假定x为假(为什么一定是假,约定俗成了)
之后沿着从ta出发的有向边进行标记
如果在标记过程中,发现有一个点的两种状态都被标记过了
那么我们之前的假设就被推翻了
需要改成x为真,重新标记
如果发现无论这个点赋值成真还是假,都会引起矛盾
可以证明这个2-SAT无解

可能我的叙述有点容易让读者yy
但是一定要注意:

这个算法没有回溯过程

这个课件讲的蛮好
%dalao的blog

下面给出代码:

struct TwoSAT{
    int n;
    vector<int> G[N*2];
    bool mark[N*2];
    int S[N*2],c;

    int dfs(int x)
    {
        if (mark[x^1]) return 0;
        if (mark[x]) return 1;    //和假设的值一样
        mark[x]=1;
        S[c++]=x;
        for (int i=0;i<G[x].size;i++)
            if (!dfs(G[x][i])) return 0;
        return 1; 
    }

    //x=xval or y=yval
    void add_clause(int x,int xv,int y,int yv)
    {
        x=x*2+xv;
        y=y*2+yv;
        G[x^1].push_back(y);
        G[y^1].push_back(x);
    }

    void init(int n)
    {
        this->n=n;
        for (int i=0;i<2*n;i++) G[i].clear();
        memset(mark,0,sizeof(mark));
    }

    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]]=0;   //清空标记 
                    if (!dfs(i+1)) return 0;
                } 
            }
        return 1;
    }
};
展开阅读全文

没有更多推荐了,返回首页