问题描述:

现有一个由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种:

  1. A[x]
  2. NOT A[x]
  3. A[x] AND A[y]
  4. A[x]  AND NOT A[y]
  5. A[x] OR A[y]
  6. A[x] OR NOT A[y]
  7.  NOT (A[x] AND A[y])
  8. NOT (A[x] OR A[y])
  9. A[x] XOR A[y]
  10. NOT (A[x] XOR A[y])
  11. A[x] XOR NOT A[y]

进一步,3可以用1表示:A[x],A[y],NOT(A[x] XOR A[y]) 相当于 NOT A[x] AND  NOT A[y]。狄摩根定律。只剩下了9中基本关系类型。

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

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

2-SAT模型建立:

1.我们利用一条有向边<i,j>,来表示选i的情况下,一定要选j;

2.用i表示某个点是true,那么i'表示某个点是false

3.因为限制的两两之间的关系,所以我们可以通过逻辑关系来建边:

1)如果给出A和B的限制关系,A和B必须一起选,(A and B)||(!A and !B )==true 那么选A必须选B,建边<i,j>和<j,i>还有<i',j'>和<j',i'>

2)如果给出A和B的限制关系,选A不能选B,那么(A && !B)||(!A && B )==true,建边<i,j'>和<j,i'>

3)如果必须选A,那么A==true,建边<i',i>

4)如果A一定不能选,那么!A==true.建边<i,i'>

这么建图之后,会出现一个有向图,这个有向图会导致一个连通环,导致某个点一旦选取,那么这条链上的所有点都要被选中。如果我们找到一个强连通分量,那么这个强连通分量当中的点,如果选取必须全部选取,不选取的话一定是全部不选取,所以只要满足这个有向图中连通的点不会导致i和i'同时被选取,如果不存在矛盾,那么当前问题就是有解的。但是往往在求解过程中,我们要求的解会要求一些性质,所以提供以下几种解决方案。

用离散的的知识解释的话就是下面这位大佬的讲解(别人发给我的)

首先,把「2」和「SAT」拆开。SAT 是 Satisfiability 的缩写,意为可满足性。即一串布尔变量,每个变量只能为真或假。要求对这些变量进行赋值,满足布尔方程。

举个例子:教练正在讲授一个算法,代码要给教室中的多位同学阅读,代码的码风要满足所有学生。假设教室当中有三位学生:Anguei、Anfangen、Zachary_260325。现在他们每人有如下要求:

  • Anguei: 我要求代码当中满足下列条件之一:

    1. 不写 using namespace std; ( ¬a)
    2. 使用读入优化 (b)
    3. 大括号不换行 ( ¬c)
  • Anfangen: 我要求代码当中满足下条件之一:
    1. 写 using namespace std; (a)
    2. 使用读入优化 (b)
    3. 大括号不换行 (
  • Zachary_260325:我要求代码当中满足下条件之一:
    1. 不写 using namespace std; (
    2. 使用 scanf (
    3. 大括号换行 (c)

我们不妨把三种要求设为 a,b,ca,b,c,变量前加 \neg¬ 表示「不」,即「假」。上述条件翻译成布尔方程即:。其中,表示或,表示与。

现在要做的是,为 ABC 三个变量赋值,满足三位学生的要求。

Q: 这可怎么赋值啊?暴力?

A: 对,这是 SAT 问题,已被证明为 NP 完全 的,只能暴力。

Q: 那么 2-SAT 是什么呢?

A: 2-SAT,即每位同学 只有两个条件(比如三位同学都对大括号是否换行不做要求,这就少了一个条件)不过,仍要使所有同学得到满足。于是,以上布尔方程当中的 c,,¬c 没了,变成了这个样子:

公式杀招:

怎么求解 2-SAT 问题?

使用强连通分量。 对于每个变量 xx,我们建立两个点:x ,¬x 分别表示变量 xx 取 true 和取 false。所以,图的节点个数是两倍的变量个数在存储方式上,可以给第 ii 个变量标号为 ii,其对应的反值标号为 i + ni+n。对于每个同学的要求  (a∨b),转换为  ¬a→b∧¬b→a。对于这个式子,可以理解为:「若 aa 假则 bb 必真,若 bb 假则 aa 必真」然后按照箭头的方向建有向边就好了。综上,我们这样对上面的方程建图:

原式 建图
 a→b∧¬b→¬a
 ¬a→b∧¬b→a
 a→¬b∧b→¬a

于是我们得到了这么一张图:

可以看到,¬a 与 b 在同一强连通分量内,a与 ¬b 在同一强连通分量内。同一强连通分量内的变量值一定是相等的。也就是说,如果 x 与 ¬x 在同一强连通分量内部,一定无解。反之,就一定有解了。

但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?

这个很简单,只需要 当 xx 所在的强连通分量的拓扑序在 \neg x¬x 所在的强连通分量的拓扑序之后取 xx 为真 就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 color[x] < color[-x] 。

代码实现:

//暴力DFS,求字典序最小的解,也是求字典序唯一的方法
#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]也要=truebool 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遍历,途径的所有点都标记//如果不能标记,那么返回falsebool 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;}
};
//联通分量+拓扑排序,求任意意一组解,比较快#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <algorithm>
#define MAXN 2000+10
#define MAXM 400000
#define INF 1000000
using namespace std;
vector<int> G[MAXN];
int low[MAXN], dfn[MAXN];
int dfs_clock;
int sccno[MAXN], scc_cnt;
stack<int> S;
bool Instack[MAXN];
int N, M;
void init()
{for(int i = 0; i < 2*N; i++) G[i].clear();
}
void getMap()
{int a, b, c;char op[10];while(M--){scanf("%d%d%d%s", &a, &b, &c, op);switch(op[0]){case 'A': if(c == 1)//a,b取1 {G[a + N].push_back(a);G[b + N].push_back(b);}else//a,b至少一个不为真 {G[a].push_back(b + N);G[b].push_back(a + N);}break;case 'O':if(c == 1)//a,b最少有一个为真 {G[b + N].push_back(a);G[a + N].push_back(b);}else//a,b都为假 {G[a].push_back(a + N);G[b].push_back(b + N);}break;case 'X':if(c == 1)//a b 不同值 {G[a + N].push_back(b);G[a].push_back(b + N);G[b].push_back(a + N);G[b + N].push_back(a);}else//a b 同真同假 {G[a].push_back(b);G[b].push_back(a);G[a + N].push_back(b + N);G[b + N].push_back(a + N);}}}
}
void tarjan(int u, int fa)
{int v;low[u] = dfn[u] = ++dfs_clock;S.push(u);Instack[u] = true;for(int i = 0; i < G[u].size(); i++){v = G[u][i];if(!dfn[v]){tarjan(v, u);low[u] = min(low[u], low[v]);}else if(Instack[v])low[u] = min(low[u], dfn[v]);}if(low[u] == dfn[u]){scc_cnt++;for(;;){v = S.top(); S.pop();Instack[v] = false;sccno[v] = scc_cnt;if(v == u) break; }}
}
void find_cut(int l, int r)
{memset(low, 0, sizeof(low));memset(dfn, 0, sizeof(dfn));memset(sccno, 0, sizeof(sccno));memset(Instack, false, sizeof(Instack));dfs_clock = scc_cnt = 0;for(int i = l; i <= r; i++)if(!dfn[i]) tarjan(i, -1);
}
void solve()
{for(int i = 0; i < N; i++){if(sccno[i] == sccno[i + N]){printf("NO\n");return ;}}printf("YES\n");
}
int main()
{while(scanf("%d%d", &N, &M) != EOF){init();getMap();find_cut(0, 2*N-1);solve();}return 0;
}

图论--2-SAT--详解相关推荐

  1. 图论2-SAT算法详解

    图论2-SAT算法详解 今天我们来介绍一个我个人认为最难的算法,这是为什么呢?肯定会有许多dalao说,不就一个2-SAT,我两分钟就A掉了.然而2-SAT的细节非常的多,稍不注意就会写错,而且测试困 ...

  2. 图论-最短路Dijkstra算法详解超详 有图解

    整体来看dij就是从起点开始扩散致整个图的过程,为什么说他稳定呢,是因为他每次迭代,都能得到至少一个结点的最短路.(不像SPFA,玄学复杂度) 但是他的缺点就是不能处理带负权值的边,和代码量稍稍复杂. ...

  3. 图论:最大流最小割详解

    最大流与最小割是十分重要且应用广泛的内容,本文用"水流"与"水管"类比,先讨论最大流问题,然后再说明最大流与最小割的特殊关系,我们还将讨论一些实际中会使用的相关 ...

  4. 脑科学研究中复杂网络测量使用和解释——图论指标详解

    脑网络研究中的图论指标详解 最全图论指标汇总 (1)度(node degree) (2)节点强度 (3)节点核心 (4)富人俱乐部系数(rich club coefficient) (5)同配系数 ( ...

  5. 图论详解——Dijkstra最短路径算法详解

    在学习dijkstra之前,如果你没学过Bellman-ford算法的话,dijkstra确实会很难懂 Bellman-ford详解 一.原理 思想 ford算法是利用动态规划的思想,而dijkstr ...

  6. [转]HTTP协议详解

    当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了We ...

  7. c枚举类型enum例题_C语言--enum,typedef enum 枚举类型详解

    原文:http://z515256164.blog.163.com/blog/static/32443029201192182854300/ 有改动 C语言详解 - 枚举类型 注:以下全部代码的执行环 ...

  8. 详解keepalived配置和使用

    详解keepalived配置和使用 一.keepalived简介: keepalived是一个类似于layer3, 4 & 5交换机制的软件,也就是我们平时说的第3层.第4层和第5层交换.Ke ...

  9. 《oracle大型数据库系统在AIX/unix上的实战详解》讨论31: oracle、sybase 数据库的不同访问...

    <Oracle大型数据库系统在AIX/UNIX上的实战详解> 讨论31:  oracle.sybase 数据库的不同访问方式   文平. 用户来信要求更细节比较一下Oracle和sybas ...

  10. HTTP协议详解 转自小坦克

    HTTP协议详解 转自小坦克 -- 有些文章是引用别人的,为了方便我以后或不再备注;引用目的是因为直接网摘里面的地址经常被重置,找不到原来的文章 当今web程序的开发技术真是百家争鸣,ASP.NET, ...

最新文章

  1. 【VB】学生信息管理系统2——窗体设计
  2. python高级开发面试题_python面试的100题(16)
  3. wireshark的使用
  4. 海南橡胶机器人成本_完成专利授权20余件!海南橡胶中橡科技搭建高标准研发平台...
  5. [Java基础]TreeSet集合概述和特点
  6. copy a random link
  7. mysql 重命名索引_mysql增删改字段,重命名替换字段
  8. CTS 2019 Pearl
  9. Python实现RFM模型
  10. PAT 1013 数素数
  11. 最新AxureUX WEB端交互原型通用组件模板库 组件仍然是这套作品的核心内容,这套作品的组件由通用组件、数据录入、数据展示、信息反馈
  12. markdown表情大全
  13. wangeditor光标乱跑,回车换行又返回来问题
  14. 由iconfont引起的svg、ttf、woff、woff2图标的研究及转换(svgs2fonts)
  15. wifi和服务器之间通信协议,wifi模块串口通信协议.doc
  16. 8000 字深度长文!B端数据可视化设计指南(信息图表篇)
  17. 概率论与数理统计---------大数定律
  18. c#教程之通过数据绑定修改数据
  19. 牛客网 18 二维数组中的查找
  20. 博士毕业选择回老家县城大专任教,事业编、副教授待遇、外加几十万安家费......

热门文章

  1. Undefined symbols for architecture i386问题解决方法
  2. BluePrism初尝2
  3. ZooKeeper学习第七期--ZooKeeper一致性原理
  4. 4-具体学习git--分支
  5. 十大技巧优化Android App性能
  6. 战痕————道具系统介绍
  7. 行、重复-SAP HANA 集合操作 UNION/Union all/INTERSECT/EXCEPT (SAP HANA Set Operations)-by小雨...
  8. 利用C#线程窗口调试多线程程序
  9. myeclipse 运行速度慢的解决方案
  10. 思想:CoreMVC是什么(3)