2-SAT 入门

一、前置技能

 >  Tarjan求割点

 >  高中数学选修2-1 命题与逻辑关系(好像是这个名字)

 >  莓了

(本文用&&表示与 || 表示或  !x表示x的否命题 与正常的数学符号不同食用请注意)

二、k-SAT

 >  Q: 为什么讲2-SAT之前要先讲k-SAT?

因为k-SAT被证明是NP-完全问题

所以暴搜就不去管它……

 >  Q: 所以什么是“SAT”?

由美国大学委员会主办的一场考试 

SAT被称为布尔方程的可满足性问题

就是判断一组逻辑关系同时满足的正确性……

比如a&&(!a) 就不满足

再比如a&&b||c&&!d&&(.....)

我们给一种特殊的布尔方程起一个名字叫 合取范式 

就是形如(a || !b || c) && (d || e) && (...)

其中每个bool变量叫 文字 打了括号的部分就叫 子句 

如果每个子句大小不超过k那么就叫 k-SAT

同理 如果合取范式的每个子句中的文字个数都不超过两个,那么对应的 SAT 问题又称为 2-SAT 问题。

三、解法

考虑一个二分图 左边表示bool变量值为0(伪) 右边表示值为1(真)

如果满足一个命题必然能推出另一个命题 那么就在这两个命题之间连一条边

考虑两个bool变量之间的关系无非是 && 和 || 

由于我们是2-SAT 所以最多出现a || b两个变量

那么转化为蕴含(推出)关系

|| 运算的定义

!a => b 且 !b => a

然后是 &&

!a => a 且 !b => b  

 >  Q: 这……bool变量还能同时满足真和伪?

当然不是 这里的推出 可以理解为如果选择了a为伪,那么最后会推出a为真

所以我们最后选择的时候不能选择a为伪 只能选择a为真 达到了我们的目的

 >  Q: 好吧……可是,为什么要这么建图?

我们考虑bool运算的性质

命题成立则它的逆否命题成立 这个性质又叫 对称性

同时考虑推出的 传递性  ,如果b是由某一个变量推知为真 和它为真的效果是一样的 都能推知下一步的变量

所以拆完真伪最终在一个强连通分量里的点的bool值相同(注意并不是变量的bool值相同)

同时上一个Q表示建图可以处理单个变量定值的问题

 >  Q:嗯……如何求出最终答案呢?

我们对于最后建好的图跑一个Tarjan缩点 如果变量的真值和伪值在同一个强连通分量里(即既不能为真也不能为伪)那么就无解

否则 每个点的答案应该是真值和伪值中拓扑序靠后的那个(同第一个Q)

(所以要缩点)

然后由于我们跑tarjan的时候 就是拓扑序靠后的强连通分量编号小

所以就不用建新图辣

四、代码

Code:(luogu P4782)

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 #define itn int
 6 #define ms(a,b) memset(a,b,sizeof a)
 7 #define rep(i,a,n) for(int i = a;i <= n;i++)
 8 #define per(i,n,a) for(int i = n;i >= a;i--)
 9 using namespace std;
10 typedef long long ll;
11 ll read() {
12     ll as = 0,fu = 1;
13     char c = getchar();
14     while(c < '0' || c > '9') {
15         if(c == '-') fu = -1;
16         c = getchar();
17     }
18     while(c >= '0' && c <= '9') {
19         as = as * 10 + c - '0';
20         c = getchar();
21     }
22     return as * fu;
23 }
24 const int N = 2000005;
25 //head
26 int n,m;
27 int head[N],nxt[N<<1],mo[N<<1],cnt = -1;
28 bool flag[N];
29 void add(itn x,int y) {
30     mo[++cnt] = y;
31     nxt[cnt] = head[x];
32     head[x] = cnt;
33 }
34 int rev(int x) {return x>n?x-n:x+n;}
35 
36 int dfn[N],low[N],col[N],stk[N];
37 int scc,idx,top;
38 bool ins[N];
39 void tarjan(int x) {
40     dfn[x] = low[x] = ++idx;
41     stk[++top] = x,ins[x] = 1;
42     for(int i = head[x];i;i = nxt[i]) {
43     int sn = mo[i];
44     if(!dfn[sn]) {
45         tarjan(sn);
46         low[x] = min(low[x],low[sn]);
47     } else if(ins[sn]) low[x] = min(low[x],dfn[sn]);
48     }
49     if(dfn[x] == low[x]) {
50     int t = -1;
51     scc++;
52     while(t!=x) {
53         t = stk[top--];
54         col[t] = scc;
55         ins[t] = 0;
56     }
57     }
58 }
59 
60 int main() {
61     n = read();
62     m = read();
63     rep(i,1,m) {
64     int x = read();x += read()*n;
65     int y = read();y += read()*n;
66     add(rev(x),y),add(rev(y),x);
67     }
68     rep(i,1,n<<1) if(!dfn[i]) tarjan(i);
69     bool flag = 1;
70     rep(i,1,n) if(col[i] == col[n+i]) flag = 0;
71     if(!flag) puts("IMPOSSIBLE");
72     else {
73     puts("POSSIBLE");
74     rep(i,1,n) printf("%d ",col[i] > col[i+n]);
75     puts("");
76     }
77     return 0;
78 }
View Code

注意:

1.跑的时候依然像Tarjan一样考虑多个联通块

2.本题全部是或的关系 注意反向建图

3.最后比较的时候每个人写法不一样要考虑大于还是小于

例题详见Practice 1

By prophetB

> 别忘了 总有人在等着你
原文地址:https://www.cnblogs.com/yuyanjiaB/p/9762501.html