2-SAT

zxh923發表於2024-07-10

前置芝士:強連通分量。

先放一個板子題:2-SAT

我們先考慮拆點,把每個變數 \(i\) 拆成兩個點,\(i\times 2\)\(i\times 2 + 1\),前一個代表這個變數 \(i\) 取假,後一個代表這個變數 \(i\) 取真。

既然有了點,我們就要考慮連邊。例如給一個條件:\(1\)\(1\) 或者 \(3\)\(0\)。我們就要建兩條邊,一條為如果 \(1\)\(0\)\(3\)\(0\),即 \(2\rightarrow 6\);另一條為如果 \(3\)\(1\)\(1\)\(1\),即 \(7\rightarrow 3\)

然後看一下這條邊表示什麼,表示的是一個要求是另一個要求的充分條件(建議學習一下充分條件與必要條件)。

那麼,如果發現圖中出現了 \(i\) 真是 \(i\) 假的充分條件;\(i\) 假是 \(i\) 真的充分條件,那麼一定無解,因為不管 \(i\) 取什麼,條件1總會要求你取 \(i\) 當前的值取反,這顯然不可能成立。

換句話說,就是 \(\forall i\in[1,n]\) 如果 \(i\times 2\)\(i\times 2 + 1\) 在一個強連通分量中,一定無解,否則有解。

說了這麼多,我們終於說完了怎麼判斷有沒有解,接下來說一下怎麼構造解。

首先我們記 \(id_i\) 表示圖中的 \(i\) 號點所在的強連通分量的編號。根據上面的描述,如果 \(\exist i\in [1,n],id_{i\times 2}=id_{i\times 2 + 1}\),無解。

否則這兩個東西的 \(id\) 陣列,一定一個大一個小(廢話)。

那麼,\(id\) 更小的那一部分可以認為是另一部分的上司(不同於尋常意義),換句話說它具有決定權,變數的值就取決於他。

這是一個小結論,證明的話建議看一些其他資料,這裡建議記住,遇到的時候直接用就可以了。

下面放一下板子題的程式碼:

#include<bits/stdc++.h>
#define int long long
#define N 2000005
#define M 2000005
using namespace std;
int n,m,h[N],e[M],ne[M],idx,dfn[N],low[N],ts,stk[N],top,id[N],cnt;
bool ins[N];
void add(int a,int b){//鏈式前向星建邊 
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void tar(int u){//經典tarjan求強連通分量 
    dfn[u]=low[u]=++ts;
    stk[++top]=u;
    ins[u]=1;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]){
            tar(j);
            low[u]=min(low[u],low[j]);
        }
        else if(ins[j])low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        cnt++;
        do{
            y=stk[top--];
            ins[y]=0;
            id[y]=cnt;
        }while(y!=u);
    }
}
signed main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int i,a,j,b;
        cin>>i>>a>>j>>b;
        i--;j--;
        add(2*i+!a,2*j+b);//按照上面說的充分條件的方式建邊 
        add(2*j+!b,2*i+a);
    }
    for(int i=0;i<n*2;i++){
        if(!dfn[i]){//tarjan 
            tar(i);
        }
    }
    for(int i=0;i<n;i++){
        if(id[i*2]==id[i*2+1]){//互為充分條件則無解 
            cout<<"IMPOSSIBLE";
            return 0;
        }
    }
    cout<<"POSSIBLE\n";//否則有解 
    for(int i=0;i<n;i++){
        if(id[i*2]<id[i*2+1]){//"上司"具有最終決定權 
            cout<<"1 ";
        }
        else cout<<"0 ";
    }
    return 0;
}