1. 程式人生 > >Luogu P4782 【模板】2-SAT 問題(2-SAT)

Luogu P4782 【模板】2-SAT 問題(2-SAT)

P4782 【模板】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\)

,表示“\(x_i\)\(a\)\(x_j\)\(b\)\((a,b\in \{ 0,1\} )\)

輸出格式:

如無解,輸出"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+n,b+n),(b,a)\)
  • \(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;
}