前置芝士:強連通分量。
先放一個板子題: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;
}