學習筆記第二十八節:2-SAT
阿新 • • 發佈:2018-12-04
正題
我又來划水了。
2-SAT問題是類似於這樣的形式:
給出n個數,m組條件,問你這m組條件是否能同時滿足。
每組條件類似於這樣的形式
怎麼做?
我們可以拆點!
把每個點拆成兩個點,一個表示選,一個表示不選。
那麼假如有一個這樣一個條件:
考慮x取1時,y必須取0;y取1時,x必須取0.
所以我們建兩條邊:。
表示的就是上面的意思。
接下來,讓我們透徹理解邊的意思。
邊的意思就是,如果a滿足,b必須滿足。
顯然不可能同時取。
在圖中怎麼表示呢?!!原來是強聯通分量
所以如果在同一強聯通分量,那麼很明顯是矛盾的。如果有,輸出Impossible。
否則就肯定存在一組解。有路徑,那麼a取,b必須取。
對於如果在一個聯通分量,那麼肯定取拓撲序大的,為什麼?因為如果取拓撲序小的,那麼拓撲序大的那個可能也會取到,就會矛盾,為了避免意外,我們選擇拓撲序大的。
如果不在一個連通分量,那麼我們取那個都無所謂。
所以,取拓撲序大的就可以了。(當然這裡的拓撲序是縮點後的拓撲序
最後,因為Tarjan是深搜,搜出來的拓撲序一定是倒序的,所以寫的時候選小的。
#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");
}