前置
\(SAT\) 是適定性( \(Satisfiability\) )問題的簡稱。一般形式為 \(k \ -\) 適定性問題,簡稱 \(k-SAT\) 。而當 \(k>2\) 時該問題為 \(NP\) 完全的。所以我們只研究 \(k=2\) 的情況。
定義
\(2-SAT\) ,簡單的說就是給出 \(n\) 個集合,每個集合有兩個元素,已知若干個 \(<a,b>\) ,表示 \(a\) 與 \(b\) 矛盾(其中 \(a\) 與 \(b\) 屬於不同的集合)。然後從每個集合選擇一個元素,判斷能否一共選 \(n\) 個兩兩不矛盾的元素。顯然可能有多種選擇方案,一般題中只需要求出一種即可。
有學長者雲:
有 \(n\) 個 \(01\) 變數 \(x_1∼x_n\),另有 \(m\) 個變數取值需要滿足的限制。
每個限制是一個 $$ 元組 \((x_{p1},x_{p2},\dots,x_{pk})\) ,滿足 \(x_{p1} \oplus x_{p2} \oplus \dots \oplus x_{pk} = a\) 。其中 \(a\) 是 \(0/1\) ,\(\oplus\) 是某種二元 \(bool\) 運算。
要求構造一種滿足所有限制的變數的賦值方案。
\(2-SAT\) 問題是通過建立圖論模型,在 \(O(n+m)\) 的時間複雜度內判斷是否有解,若有解可以構造出一組合法解。
思路
值得注意的是在不同的題目中二元 \(bool\) 運算可能有差異,但是建圖的基本思路大致相同。
來觀摩一組 OI-Wiki
的例子:
比如邀請人來吃喜酒,夫妻二人必須去一個,然而某些人之間有矛盾(比如 \(A\) 先生與 \(B\) 女士有矛盾, \(C\) 女士不想和 \(D\) 先生在一起),那麼我們要確定能否避免來人之間沒有矛盾,有時需要方案。這是一類生活中常見的問題。
使用布林方程表示上述問題。設 \(a\) 表示 \(A\) 先生去參加,那麼 \(B\) 女士就不能參加( \(\lnot a\)); \(b\) 表示 C 女士參加,那麼 \(\lnot b\) 也一定成立( \(D\) 先生不參加)。總結一下,即 \((a \lor b)\) (變數 \(a\) , \(b\) 至少滿足一個)。對這些變數關係建有向圖,則有:\(\lnot a \Rightarrow b \land \lnot b \Rightarrow a\) ( \(a\) 不成立則 \(b\) 一定成立;同理,\(b\) 不成立則 \(a\) 一定成立)。建圖之後,我們就可以使用縮點演算法來求解 \(2-SAT\) 問題了。
核心
Tarjan 縮點大法
\(Tarjan\) 大法好!
主要還是考慮如何更合適地建圖。
再來一組例子:
假設有 \(a_1\) 、 \(b_2\) 和 \(a_2\) 、 \(b_1\) 兩對,已知 \(a_1\) 和 \(b_2\) 間有矛盾,於是為了方案自洽,由於兩者中必須選一個,所以我們就要拉兩條有向邊 \((a_1,b_1)\) 和 \((b_2,a_2)\) 表示選了 \(a_1\) 則必須選 \(b_1\) ,選了 \(b_2\) 則必須選 \(a_2\) 才能夠自洽。
然後通過這樣建邊再跑一遍 \(Tarjan\) 判斷是否有一個集合中的兩個元素在同一個強連通分量中,若有則不可能,否則輸出方案。構造方案只需要把幾個不矛盾的強連通分量拼起來就好了。
-
輸出方案時可以通過變數在圖中的拓撲序確定該變數的取值。如果變數 \(\lnot x\) 的拓撲序在 \(x\) 之後,那麼取 \(x\) 值為真。應用到 \(Tarjan\) 演算法的縮點,即 \(x\) 所在強連通分量編號在 \(\lnot x\) 之前時,取 \(x\) 為真。因為 \(Tarjan\) 演算法求強連通分量時使用了棧,所以 \(Tarjan\) 求得的強連通分量編號相當於反拓撲序。
-
時間複雜度為 \(O(n+m)\) 。
暴力DFS
\(Tarjan\) 好? \(DFS\) 表示不服。\(DFS\) 大法妙!
直接選取圖上一個點,沿著一條路徑搜下去,如果一個點被選擇了,那麼這條路徑以後的點都將被選擇。
如果出現一個集合中的兩者都被選擇了,那麼此即為矛盾情況。
例題
真·模板題
思路
-
這是一道模板題。
-
顯而易見每個變數 \(x_i\) 都可以被分開儲存,即拆分成 \(i\) 和 \(i+n\) ,分別表示 \(x_i=1\) 和 \(x_i=0\) ,則這兩個事件是互斥的。
-
對於限制 \(x_i\) 的每個命題 \(a\) 和 \(b\) ,一定有一個為真,則可以寫成
那麼由此可連邊建圖 \((\lnot a,b)\) , \((\lnot b,a)\) 。
-
在該圖中,若節點 \(i\) 與 \(i+n\) 在同一個強連通分量中,即允許互相到達,則它們分別代表的互斥事件會同時發生,說明存在矛盾,即不存在一組合法的方案。
-
否則則有解。
-
構造合法解:
-
對原圖縮點得到一張 \(DAG\) 。
-
對於變數 \(x_i\),考察節點 \(i\) 與 \(i+n\) 所在強連通分量的拓撲關係。若兩分量不連通,則 \(xi\) 可取任意一個值。否則只能取屬於拓撲序較大的分量的值。因為若取拓撲序較小的值,可以根據邏輯關係推出取另一個值也是同時發生的。
by @LuckyBlock
-
-
\(Tarjan\) 演算法賦給強連通分量的編號順序與拓撲序是相反的,上文已有說明。
CODE
/*
Name: P4782 【模板】2-SAT 問題
By Frather_
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <stack>
using namespace std;
/*=========================================快讀*/
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
/*=====================================定義變數*/
int n, m;
const int _ = 5000050;
struct edge
{
int to;
int nxt;
} e[_];
int cnt, head[_];
int dfn[_], low[_], num;
int bel[_], b_num;
stack<int> s;
/*===================================自定義函式*/
void add(int from, int to)
{
e[++cnt].to = to;
e[cnt].nxt = head[from];
head[from] = cnt;
}
void Tarjan(int u_)
{
dfn[u_] = low[u_] = ++num;
s.push(u_);
for (int i = head[u_]; i; i = e[i].nxt)
{
int v_ = e[i].to;
if (!dfn[v_])
{
Tarjan(v_);
low[u_] = min(low[u_], low[v_]);
}
else if (!bel[v_])
low[u_] = min(low[u_], dfn[v_]);
}
if (dfn[u_] == low[u_])
{
b_num++;
while (true)
{
int t = s.top();
s.pop();
bel[t] = b_num;
if (t == u_)
break;
}
}
return;
}
/*=======================================主函式*/
int main()
{
n = read();
m = read();
for (int i = 1; i <= m; i++)
{
int x = read();
int a = read();
int y = read();
int b = read();
if (a && b)
{
add(x + n, y);
add(y + n, x);
}
if (!a && b)
{
add(x, y);
add(y + n, x + n);
}
if (!a && !b)
{
add(x, y + n);
add(y, x + n);
}
if (a && !b)
{
add(x + n, y + n);
add(y, x);
}
}
for (int i = 1; i <= n * 2; i++)
{
if (!dfn[i])
Tarjan(i);
if (i <= n && bel[i] == bel[i + n])
{
printf("IMPOSSIBLE\n");
return 0;
}
}
printf("POSSIBLE\n");
for (int i = 1; i <= n; i++)
printf("%d ", bel[i] < bel[i + n]);
return 0;
}
寫在最後
-
最近被教練抓頹抓得好苦啊\kk
-
再次向已逝去的學長致敬(((不是
-
鳴謝:
-
《演算法競賽進階指南》
-
@LuckyBlock
-