2-SAT 总结
2-SAT 问题详解
2-SAT 的典型形式
每个约束条件长这样:
(x₁ 为真) 或 (x₂ 为假)
(x₃ 为真) 或 (x₄ 为真)
(变量a = 某值) 或 (变量b = 某值)
用逻辑符号表示就是:(a ∨ b) 或者 (a ∨ ¬b) 等等。
核心思想:转化为图论问题
第一步:建立蕴含图
关键公式:(a ∨ b) 等价于 (¬a → b) ∧ (¬b → a)
意思是:如果 a 为假,那么 b 必须为真;如果 b 为假,那么 a 必须为真。
直觉理解:约束 (a ∨ b) 说“a 和 b 至少一个为真”。那如果我发现 a 是假的,为了满足这个约束,b 就必须是真的。反过来也一样。
第二步:图的构建
- 每个变量
x拆成两个点:x表示 x 为真,¬x表示 x 为假 - 对于约束
(a ∨ b),添加两条有向边:¬a → b(a 假则 b 真)¬b → a(b 假则 a 真)
为什么不是
¬a → b(a 假则 b 真)a → ¬b(a 真则 b 假)
呢?因为其实可以 a 和 b 都是真,这样建图会导致 a 和 b 必然一正一负,显然不符。
当然,我们也可以简单建图,背下来即可:
adde(x+n*(vx&1),y+n*(vy^1));
adde(y+n*(vy&1),x+n*(vx^1));
第三步:判断可满足性
如果存在变量 x,使得 x 和 ¬x 在同一个强连通分量(SCC)中,那么问题无解。
为什么? 因为在 SCC 中,所有点可以互相到达。x 能到达 ¬x 意味着“x 为真能推出 x 为假”,反之亦然。这产生了逻辑矛盾:x 真 ⇒ x 假,同时 x 假 ⇒ x 真。无论 x 取什么值都会导出相反的要求,所以不可能满足。
第四步:构造解
如果可满足,按照 SCC 的拓扑序(从后往前)赋值:如果 x 所在 SCC 的拓扑序在 ¬x 之后,则 x 为真,否则为假。
为什么这样赋值正确? 一条边 u → v 的逻辑含义是“u 真则 v 真”。在拓扑序中,边总是从序号小的 SCC 指向序号大的 SCC。如果我们按拓扑序从大到小(后往前)处理,先决定的点不会依赖后决定的点。当我们处理 x 时,如果 x 的拓扑序比 ¬x 大,说明 ¬x → x 的路径不存在(否则它们会在同一 SCC,或者 ¬x 的拓扑序更大),那我们把 x 设为真就不会被迫把 ¬x 也设为真,从而避免矛盾。
不会证明也没有关系,试一试就行。
一个简单的验证:
3 2
1 1 3 0
1 1 3 1
第一位应该是 1。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,tot;
ll dfn[2000005],low[2000005],st[2000005],tail,in[2000005],scccnt,id[2000005];
vector<ll>ve[2000005];
void tarjan(ll x){
dfn[x]=low[x]=++tot;
st[++tail]=x;
in[x]=1;
for(int v:ve[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(in[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
scccnt++;
ll y;
do{
y=st[tail];
tail--;
in[y]=0;
id[y]=scccnt;
}while(y!=x);
}
}
void adde(ll x,ll y){
ve[x].push_back(y);
}
int main(){
cin>>n>>m;
ll x,vx,y,vy;
for(int i=1;i<=m;i++){
cin>>x>>vx>>y>>vy;
adde(x+n*(vx&1),y+n*(vy^1));
adde(y+n*(vy&1),x+n*(vx^1));
}
for(int i=1;i<=n*2;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
if(id[i]==id[i+n]){
cout<<"IMPOSSIBLE";
return 0;
}
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;i++){
if(id[i]<id[i+n]){
cout<<1<<" ";
}
else{
cout<<0<<" ";
}
}
}
可以自由转载

浙公网安备 33010602011771号