题解 P4782 【【模板】2-SAT 问题】
前言(feihua)
SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k-SAT
可以证明,当k>2时,k-SAT是NP完全的。因此一般讨论的是k==2的情况,即2-SAT问题。
2-SAT用处 :有若干个包含2个元素的集合,只能选择每个集合中的一个元素,并给出一些条件(例:选a后不能选b),要求求出满足这些条件的选择,这个解也称为2-SAT的解
如何求解?
如上图,为{A,A'},{B,B'},{C,C'}这几个集合
若给出条件,{A,B'},{B‘C’}不能同时选择
因为{A,A'},{B,B'},{C,C'}中,每个集合都要选出一个元素,即不能同时选择
又因为{A,B'}不能同时选择,则选择B‘时,一定选择A’,于是在B‘,A’之间连上一条有向边(因为选A‘不一定B’);同理,在选B时也一定选A
其它也同上处理,于是: 接着再进行缩点,然而这个栗子中没有可缩的点,判断对称的两点是否在同一个强连通分量中,若在,则无解
若不在,我们接着应该求出符合要求的解,如何求呢
1.进行拓扑排序(不用了)我们可以发现,缩点后强连通分量的编号就是反着的拓扑排序
为什么要拓扑排序呢?比如一个样例,它让A->B->A',显然我们不能选A,只能选A’(拓扑排序大的),即选强连通分量编号小的
2.对于每一个集合,选出强连通分量小的
上AC代码:
#include<bits/stdc++.h>
using namespace std;
int head[2000006],zhan[2000006],color[2000006],dfn[2000006],low[2000005];
int num,Color,dfx,top;
bool pd[2000005];
struct{
int to,next;
}a[2000006];
void lian(int from,int to){
num++;
a[num].to=to;
a[num].next=head[from];
head[from]=num;
}
void tarjan(int x){
int i;
dfn[x]=low[x]=++dfx;zhan[++top]=x;
pd[x]=1;
for(i=head[x];i;i=a[i].next){
if(pd[a[i].to]==0){
tarjan(a[i].to);
low[x]=min(low[x],low[a[i].to]);
}
if(color[a[i].to]==0)low[x]=min(low[x],dfn[a[i].to]);
}
if(low[x]==dfn[x]){
Color++;
do{
i=zhan[top];top--;
color[i]=Color;
}while(low[i]!=dfn[i]);
}
}
int main(){
int n,m,i,j,a,_a,b,_b;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d%d%d",&a,&_a,&b,&_b);
if(_a==0&&_b==0)lian(a+n,b),lian(b+n,a);
else if(_a==1&&_b==0)lian(a,b),lian(b+n,a+n);
else if(_a==0&&_b==1)lian(a+n,b+n),lian(b,a);
else lian(a,b+n),lian(b,a+n);
}
for(i=1;i<=2*n;i++){
if(color[i]==0)tarjan(i);
}
for(i=1;i<=n;i++){
if(color[i]==color[i+n]){
printf("IMPOSSIBLE\n");
return 0;
}
}
printf("POSSIBLE\n");
for(i=1;i<=n;i++){
if(color[i]<color[i+n])printf("0 ");
else printf("1 ");
}
return 0;
}