Luogu P4782 【模板】2-SAT 問題(2-SAT)
題意
題目背景
\(2-SAT\)問題模板
題目描述
有\(n\)個布林變數\(x_1\sim x_n\),另有\(m\)個需要滿足的條件,每個條件的形式都是“\(x_i\)為\(true/false\)或\(x_j\)為\(true/false\)”。比如“\(x_1\)為真或\(x_3\)為假”、“\(x_7\)為假或\(x_2\)為假”。\(2-SAT\)問題的目標是給每個變數賦值使得所有條件得到滿足。
輸入輸出格式
輸入格式:
第一行兩個整數\(n\)和\(m\),意義如題面所述。
接下來\(m\)行每行\(4\)個整數\(i\ a\ j\ b\)
輸出格式:
如無解,輸出"IMPOSSIBLE"
(不帶引號); 否則輸出"POSSIBLE"
(不帶引號),下一行\(n\)個整數\(x_1\sim x_n(x_i\in \{ 0,1\} )\),表示構造出的解。
輸入輸出樣例
輸入樣例#1:
3 1
1 1 3 0
輸出樣例#1:
POSSIBLE
0 0 0
思路
快學\(2-SAT\),這樣你就可以做[NOI2017]遊戲這道水題了。 --huyufeifei
\(2-SAT\)問題是我很喜歡的一類問題,一是因為它使用了我很喜歡的\(Tarjan\)
,二是它使用邏輯判斷的方式實現的演算法,這也是很使我喜歡的。
對於每一個\(x_i\)我們建兩個點,編號為\(i\)和\(i+n\),\(i\)表示\(x_i=1\)的情況,\(i+n\)表示\(x_i=0\)的情況。接下來考慮對於每一對邏輯關係建邊。在這裡,為了問題的普適性,我們不止考慮題目列出的條件,來試著考慮更多的情況。
- \(a\)為真:建立一條邊\((a+n,a)\),表示如果\(a\)為假,則\(a\)為真。這樣就可以最終推得\(a\)為真的情況。
- 如果\(a\)為真,則\(b\)為假:建立兩條邊:\((a,b+n),(b,a+n)\)。
- \(a\)為真與\(b\)
- \(a\)為真與\(b\)為假不能同時滿足:建立兩條邊:\((a,b),(b+n,a+n)\)。
還有很多的情況沒有列舉,不過它們與上述內容形似,在這裡就不做列舉了。
接下來怎麼辦呢?根據我們連邊的方式,不難發現邊的意義為推匯出,也就是說,如果\(a\)能通過某些路徑到達\(b\),這表示的意義就是\(a\)能通過某些條件推匯出\(b\),那麼如果我們讓\(a\)滿足,\(b\)就一定要被滿足。如果\(a,b\)能夠互達,就說明這兩者要麼同時被滿足,要麼同時不被滿足。
不難想出,有且僅有一種情況無解:\(a\)與\(a+n\)可以互達,也就是兩個互相矛盾的條件可以互相推匯出。使用\(Tarjan\)縮點,這樣可以快速求出任意兩點是否可以互相到達,也就可以判斷出解的存在性。
如何決定各個變數的取值呢?如果能從\(a\)推匯出\(a+n\),我們顯然不能選擇\(a\),而只能選擇\(a+n\)。所以對於同一個變數的兩個取值,我們要檢查其是否有推導的關係。根據\(Tarjan\)演算法的特性,如果\(a\)能到達\(b\)且\(a,b\)不在同一縮出的點中,那麼\(b\)縮點之後所在點的編號一定小於\(a\)。如果\(a\)不能到達\(b\),那麼兩者的縮點編號不好判斷。當然,既然只需要得出任意一組解,對於每一對\((a,a+n)\),我們就輸出其縮點編號小的即可。
AC程式碼
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e6+5;
int n,m,tot,dfn[MAXN],low[MAXN];
int cnt,top[MAXN],to[MAXN],nex[MAXN];
int js,bel[MAXN];
bool vis[MAXN];
stack<int>S;
int read()
{
int re=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
return re;
}
void add_edge(int x,int y){to[++cnt]=y,nex[cnt]=top[x],top[x]=cnt;}
void tarjan(int now)
{
dfn[now]=low[now]=++tot,vis[now]=true;
S.push(now);
for(int i=top[now];i;i=nex[i])
if(!dfn[to[i]]) tarjan(to[i]),low[now]=min(low[now],low[to[i]]);
else if(vis[to[i]]) low[now]=min(low[now],dfn[to[i]]);
if(dfn[now]==low[now])
{
bel[now]=++js,vis[now]=false;
while(S.top()!=now) bel[S.top()]=js,vis[S.top()]=false,S.pop();
S.pop();
}
}
int main()
{
n=read(),m=read();
while(m--)
{
int x=read(),xx=read(),y=read(),yy=read();
if(xx&&yy) add_edge(x+n,y),add_edge(y+n,x);
else if(xx&&!yy) add_edge(x+n,y+n),add_edge(y,x);
else if(!xx&&yy) add_edge(x,y),add_edge(y+n,x+n);
else if(!xx&&!yy) add_edge(x,y+n),add_edge(y,x+n);
}
for(int i=1;i<=(n<<1);i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
if(bel[i]==bel[i+n])
{
printf("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=1;i<=n;i++) printf("%d ",bel[i]<bel[i+n]);
return 0;
}