部分内容转自luogu神犇题解:https://www.luogu.org/problemnew/solution/P4782
2-SAT定义:
即给定一些布尔方程,每个变量只能为 t r u e true true or f a l s e false false
要求对这些变量进行赋值,满足布尔方程。
简单来说就是离散数学中的布尔方程例如: ( ¬ a ∨ b ∨ ¬ c ) ∧ ( a ∨ b ∨ ¬ c ) ∧ ( ¬ a ∨ ¬ b ∨ c ) (¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c) (¬a∨b∨¬c)∧(a∨b∨¬c)∧(¬a∨¬b∨c)
现在要做的是,为 ABC 三个变量赋值,满足三位学生的要求。
但这是 SAT 问题,已被证明为 NP。
那么 2-SAT 是什么呢?
2-SAT,即只有两个条件,例如布尔方程当中的 c , ¬ c c,\neg c c,¬c 没了,变成了这个样子: ( ¬ a ∨ b ) ∧ ( a ∨ b ) ∧ ( ¬ a ∨ ¬ b ) (\neg a\vee b) \wedge (a\vee b) \wedge (\neg a\vee\neg b) (¬a∨b)∧(a∨b)∧(¬a∨¬b)
建图:
x x x和 y y y至少选一个: ( x , 0 , y , 1 ) , ( y , 0 , x , 1 ) (x,0,y,1),(y,0,x,1) (x,0,y,1),(y,0,x,1)
x x x和 y y y不能同时选: ( x , 1 , y , 0 ) , ( y , 1 , x , 0 ) (x,1,y,0),(y,1,x,0) (x,1,y,0),(y,1,x,0)
选了 x x x就必须选 y y y: ( x , 1 , y , 1 ) , ( y , 0 , x , 0 ) (x,1,y,1),(y,0,x,0) (x,1,y,1),(y,0,x,0)
那么如何解决2-sat问题呢?
用图论中的强连通分量。
对于每个变量 x x x,我们建立两个点: ¬ x ¬x ¬x 分别表示变量 x x x 取 true 和取 false。所以,图的节点个数是两倍的变量个数。在存储方式上,可以给第 i i i 个变量标号为 i i i,其对应的反值标号为 i + n i + n i+n。对于每个同学的要求 ( a ∨ b ) (a∨b) (a∨b),转换为 ¬ a → b ∧ ¬ b → a ¬a→b∧¬b→a ¬a→b∧¬b→a。对于这个式子,可以理解为:「若 a 假则 b 必真,若 b 假则 a 必真」然后按照箭头的方向建有向边就好了。综上,我们这样对上面的方程建图.
同一强连通分量内的变量值一定是相等的。也就是说,如果 x 与 ¬ x ¬x ¬x 在同一强连通分量内部,一定无解。反之,就一定有解了。
但是,对于一组布尔方程,可能会有多组解同时成立。要怎样判断给每个布尔变量赋的值是否恰好构成一组解呢?
这个很简单,只需要 当 x x x 所在的强连通分量的拓扑序在 ¬ x ¬x ¬x 所在的强连通分量的拓扑序之后取 x x x 为真 就可以了。在使用 Tarjan 算法缩点找强连通分量的过程中,已经为每组强连通分量标记好顺序了——不过是反着的拓扑序。所以一定要写成 c o l o r [ x ] < c o l o r [ − x ] color[x] < color[-x] color[x]<color[−x] 。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e6+5;
int dfn[maxn<<1],low[maxn<<1],sta[maxn<<1],vis[maxn<<1],dfnnum;
int stalen;
int n;
int tag[maxn<<1];
struct Edge{
int u,v,nxt;
}edge[maxn<<1];
int head[maxn<<1],tot;
inline void init(){
memset(head,-1,sizeof(head));
tot = 0;
}
inline void addedge(int u,int v){
edge[++tot] = {
u,v,head[u]};
head[u] = tot;
}
inline void checkadd(int i,int a,int j,int b){
addedge(i+n*(a&1),j+n*(b^1));
addedge(j+n*(b&1),i+n*(a^1));
}
int num;
void tarjan(int u){
dfn[u] = low[u] = ++dfnnum;
sta[stalen++] = u,vis[u] = 1;
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(!dfn[e.v]){
tarjan(e.v);
low[u] = min(low[u],low[e.v]);
}else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
}
if(low[u] == dfn[u]){
num++; int cur;
do{
cur = sta[--stalen];
vis[cur] = 0;
tag[cur] = num;
}while(cur!=u);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init();
int m; cin >> n >> m;
for(int i = 0; i < m; ++i){
int ii,a,j,b; cin >> ii >> a >> j >> b;
checkadd(ii,a,j,b);
}
for(int i = 1; i <= 2*n;