【学习笔记】2-SAT 小结

2-SAT 小结

从SAT说起

布尔可满足性问题(Boolean satisfiability problem;SAT))属于决定性问题,也是第一个被证明属于NP完全的问题。 此问题在计算机科学上许多领域的皆相当重要,包括计算机科学基础理论、算法、人工智能、硬件设计等等。---摘自某百科

如:有三个bool变量,三个人要求的取值如下

A:101

B:110

C:111

如果三个人要被满足至少一个取值,求方案。

虽然SAT是NP完全问题,不过今天介绍的\(2-SAT\)是特殊的,其可以在\(O(n+m)\)的时间完成。

前置知识:tarjan(强联通分量),一定的逻辑思维

部分定义:

取值:本文中一个变量的取值有且只有\(0\)\(1\),即\(bool\)变量

\(tarjan\)编号:指求强联通分量时,求出的强联通分量编号。

热身

  • 请思考一下,如果给一条类似"a选1或b选0"这种约束,那么能推出什么?

请注意:这里的或指的是逻辑或,即两者至少满足一条,而非二选一。

显然,我们可以得出

1:如果a选0,则b必须选1
2:如果b选1,则a必须选1

  • 如果是"a如果选1则b必须选0呢"?
    1:如果a选1,则b必须选0
    2:如果b选1,则a必须选0
  • 如果是"a必须选1"呢?

这个留给读者自行思考

建边

我们可以通过建边将其表达出来。\((u,v)\)的意义是,假设选择了\(u\),则必须\(v\)。那么我们就可以按照上面所述进行建边。

我们将每个变量拆成两点,对于\(1 \le i \le n\)\(i\)的意义是第\(i\)个变量选\(0\)\(i+n\)为选\(1\)

那么,我们就得到一张完整的有向图了。

我们可以发现:对于一个环,只要选定一个点,那么其他点也随之确定。所以,我们可以通过强联通分量,使其成为一张有向无环图!

求解

假设\(i\)\(i+n\)在同一个强联通分量内,则表示“假设第\(i\)个变量选0,那么就要选\(1\)”(或反过来),这时一定无解。

(那么此时上面的谜底也出来了,如果\(i\)必须为\(0\),则建边\((i+n,i)\),如果必须选\(i=1\),则无解)

那么我们怎么输出解呢?

这时候我们请出拓扑序。如果对于两个点\(u,v\),假设\(u\)的拓扑序小于\(v\),那么没有\(v\)没有到\(u\)的路径。证明略。

所以,假设对于\(i\),选拓扑序小的可能会出问题,所以应选拓扑序大的

Q:拓扑好麻烦啊,能否不拓扑啊qwq

A:可以!考虑直接使用\(tarjan\)寻强联通分量时留下来的编号,因为\(tarjan\)寻环是\(dfs\)下去的,与拓扑恰恰相反,所以,我们应选\(tarjan\)编号小的

Q:讲了这么多,上代码吧~

代码

题目链接

这题只有一个约束,即“或”约束,按上文所写即可。

#include<bits/stdc++.h>
using namespace std;
int ltk,cc,to[2000500],net[2000500],fr[2005000],dfn[2000500],low[2005000],snack[2005000],color[2005000];
int df,p,u,v,u1,v1,n,m;bool  vis[2000500]; 
void addedge(int u,int v)
{
    cc++;
    to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++df;
	snack[++p]=x;vis[x]=true;
	for (int i=fr[x];i;i=net[i])
    {
    	int y=to[i];
    	if (!dfn[y])
    	{
    		tarjan(y);
    		low[x]=min(low[x],low[y]);
    	}
    	else 
		  if (vis[y]) low[x]=min(low[x],dfn[y]);
    }
    if  (dfn[x]==low[x])
    {
         ltk++;
         while (snack[p]!=x)
         {
               color[snack[p]]=ltk;vis[snack[p]]=false;
               p--;
         }
         color[x]=ltk;vis[snack[p]]=false;
         p--;
    }
}//tarjan
int main()
{
    cin>>n>>m;
    for (int i=0;i<m;i++)
    {
        scanf("%d%d%d%d",&u,&u1,&v,&v1);
        addedge(u+(u1^1)*n,v+v1*n);//位运算,意义与上文相同
        addedge(v+(v1^1)*n,u+u1*n);
    }
    for (int i =1;i<=2*n;i++)
    {
    	p=0;
        if (!dfn[i])
            tarjan(i);
    }
    for (int i=1;i<=n;i++)
    {
        if (color[i]==color[i+n])
        {
            cout<<"IMPOSSIBLE";
            return 0;
        }
    }
    cout<<"POSSIBLE\n";
    for (int i=1;i<=n;i++)
    {
        if (color[i]<color[i+n])
        {
             cout<<0<<" ";
        }
       else
       {
           cout<<1<<" ";
       }
    }
	return 0;
}

参考资料:

[1]

[2]LuoguP4782题解

posted @ 2019-02-04 22:22  fmj_123  阅读(600)  评论(0编辑  收藏  举报