2-SAT
2-SAT
引入
我们有很多个变量,分别为 x 1 , x 2 , … , x n x_1,x_2,\dots,x_n x1,x2,…,xn
每个变量有真假两种情况
我们有几个条件,由几个变量或起来,可以添加非,如:
- x 1 ∪ x 2 ∪ x 3 x_1∪ x_2∪ x_3 x1∪x2∪x3
- ¬ x 2 ∪ ¬ x 3 ∪ x 4 \neg x_2∪\neg x_3∪ x_4 ¬x2∪¬x3∪x4
在SAT问题中,我们求出这些变量的取值,满足许多个这样的条件
然而不幸的是,SAT问题是一个NPC问题,我们没有办法在多项式时间内求出解
不过,对于2-SAT问题,我们有比较好的解法
在2-SAT问题中,我们在条件中只会涉及到两个变量,如:
- x 1 ∪ x 3 x_1∪ x_3 x1∪x3
- ¬ x 3 ∪ x 4 \neg x_3∪ x_4 ¬x3∪x4
有了这个条件限制,我们可以在 O ( n + m ) O(n+m) O(n+m)的复杂的内求解
比较奇怪是,我们使用tarjan算法求解
例题
a
∪
b
⟺
¬
a
⇒
b
¬
b
⇒
a
a ∪ b\Longleftrightarrow \neg a\Rightarrow b\space \space \neg b \Rightarrow a
a∪b⟺¬a⇒b ¬b⇒a
于是我们对于
¬
a
→
b
\neg a\rightarrow b
¬a→b建一条有向边,
¬
b
→
a
\neg b \rightarrow a
¬b→a建一条有向边
一个例子:
如何判断无解呢?
对于一个点 x 1 x_1 x1如果我们当前路径经过了 ¬ x 1 \neg x_1 ¬x1,并且沿着 ¬ x 1 \neg x_1 ¬x1走能到达 x 1 x_1 x1
也就是说, x 1 = 1 x_1=1 x1=1时,则 ¬ x 1 \neg x_1 ¬x1必须取1, ¬ x 1 \neg x_1 ¬x1取1,则 x 1 x_1 x1必须取1
因此当变量 x i x_i xi与 ¬ x i \neg x_i ¬xi处于同一个强连通分量中时无解
因此我们选用tarjan
哪如果每一对 x i x_i xi与 ¬ x i \neg x_i ¬xi都不在用一个强连通分量中时,是不是一定有解呢?
答案是肯定的
集体来说,我们枚举所有点
我们进行缩点,得到一张拓扑图
我们进行拓扑排序
对于 x i x_i xi与 ¬ x i \neg x_i ¬xi
我们找到他们各自所在的连通块(缩完点之后的点)
找到他们在拓扑排序中被遍历到的顺序
我们让顺序靠后的连通块成立
就OK了
/*************************************************************************
> File Name: [模板]2-SAT问题.cpp
> Author: Typedef
> Mail: 1815979752@qq.com
> Created Time: 2021/3/4 22:23:14
> Tags: 2-SAT tarjan
************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=2000010,M=2000010;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],ts,stk[N],top;
int id[N],cnt;
bool ins[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u){
dfn[u]=low[u]=++ts;
stk[++top]=u,ins[u]=true;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}else if(ins[j]) low[u]=min(low[u],dfn[j]);
}
if(low[u]==dfn[u]){
int y;
cnt++;
do{
y=stk[top--],ins[y]=false,id[y]=cnt;
}while(y!=u);
}
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
while(m--){
int i,a,j,b;
scanf("%d%d%d%d",&i,&a,&j,&b);
i--,j--;
add(2*i+!a,2*j+b);
add(2*j+!b,2*i+a);
}
for(int i=0;i<n*2;i++)
if(!dfn[i])
tarjan(i);
for(int i=0;i<n;i++)
if(id[i*2]==id[i*2+1]){
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=0;i<n;i++)
if(id[i*2]<id[2*i+1]) printf("0 ");
else printf("1 ");
system("pause");
return 0;
}