正题
我又来划水了。
2-SAT问题是类似于这样的形式:
给出n个数,m组条件,问你这m组条件是否能同时满足。
每组条件类似于这样的形式
怎么做?
我们可以拆点!
把每个点拆成两个点,一个表示选,一个表示不选。
那么假如有一个这样一个条件:
考虑x取1时,y必须取0;y取1时,x必须取0.
所以我们建两条边:。
表示的就是上面的意思。
接下来,让我们透彻理解边的意思。
边的意思就是,如果a满足,b必须满足。
显然不可能同时取。
在图中怎么表示呢?!!原来是强联通分量!首先,强联通分量的要么同时取要么同时不取。
所以如果在同一强联通分量,那么很明显是矛盾的。如果有,输出Impossible。
否则就肯定存在一组解。有路径,那么a取,b必须取。
对于如果在一个联通分量,那么肯定取拓扑序大的,为什么?因为如果取拓扑序小的,那么拓扑序大的那个可能也会取到,就会矛盾,为了避免意外,我们选择拓扑序大的。
如果不在一个连通分量,那么我们取那个都无所谓。
所以,取拓扑序大的就可以了。(当然这里的拓扑序是缩点后的拓扑序
最后,因为Tarjan是深搜,搜出来的拓扑序一定是倒序的,所以写的时候选小的。
20190813upd:这个2-SAT不是严格2-SAT问题,这种问题要满足一个条件:交换律。
也就是说,若不能同时取,那么不能同时取。这个问题有时候会在很多问题中很麻烦。
那么就不存在一个状态可以到达另外两个状态,因为那样的话,都必然到。那么就必然在同一个强连通分量里面。无解。
还有很多很多的细节,但是总是可以被证明出来:在这种不严格的2-sat的形式中,按照标准做法总是可以解决。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<stack>
using namespace std;
int n,m;
struct edge{
int y,next;
}s[2000010];
int first[2000010],len=0;
int t=0,T=0;
int dfn[2000010],low[2000010],tim[2000010];
bool tf[2000010];
stack<int> f;
void ins(int x,int y){
len++;
s[len]=(edge){y,first[x]};first[x]=len;
}
void Tarjan(int x){
dfn[x]=low[x]=++t;
f.push(x);tf[x]=true;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(!dfn[y]){
Tarjan(y);
low[x]=min(low[y],low[x]);
}
else if(tf[y]) low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
T++;
int d;
tim[x]=T;
tf[x]=false;
while((d=f.top())!=x){
f.pop();
tim[d]=T;
tf[d]=false;
}
f.pop();
}
}
int main(){
scanf("%d %d",&n,&m);
int x,a,y,b;
for(int i=1;i<=m;i++){
scanf("%d %d %d %d",&x,&a,&y,&b);
if(a==0 && b==0) ins(x,y+n),ins(y,x+n);
if(a==0 && b==1) ins(x,y),ins(y+n,x+n);
if(a==1 && b==0) ins(x+n,y+n),ins(y,x);
if(a==1 && b==1) ins(x+n,y),ins(y+n,x);
}
for(int i=1;i<=2*n;i++)
if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++)
if(tim[i]==tim[i+n]){
printf("IMPOSSIBLE");
return 0;
}
printf("POSSIBLE\n");
for(int i=1;i<=n;i++)
printf("%d ",tim[i]<tim[i+n]);
printf("\n");
}