• 【学习笔记】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
    ";
        for (int i=1;i<=n;i++)
        {
            if (color[i]<color[i+n])
            {
                 cout<<0<<" ";
            }
           else
           {
               cout<<1<<" ";
           }
        }
    	return 0;
    }
    

    参考资料:

    [1]

    [2]LuoguP4782题解

  • 相关阅读:
    [转+]C语言复杂声明
    c和c++数组初始化一点小区别
    [转]Linux ftp命令的使用方法
    Ubuntu 12.04 英文版中文输入法设置
    [转]Android手机中获取手机号码和运营商信息
    把google地圖放在Crm Entity中
    为什么报表里面记录的创建时间 比我们电脑客户端的世界时间 隔8个小时?这个是什么原因?
    print style Iframe
    取出MSCRM父窗口的欄位的值
    Display Fetch in IFRAME – Part 2
  • 原文地址:https://www.cnblogs.com/fmj123/p/10352305.html
Copyright © 2020-2023  润新知