(本文可能需要一點

離散數學

基礎)

SAT

,即布林可滿足性問題,是判定一個

命題公式

是否為

可滿足式

[1]

的問題。這個問題被證明是

NP-完全

的(相關科普可參考這篇知乎文章),目前沒有多項式時間內的解法。

我們轉而考慮SAT的簡單版本,即命題公式為

合取正規化

[2]

,且組成它的每個

簡單析取式

[2]

都至多含有

n

文字

[2]

,這一問題稱為

n-SAT

。當

n\ge3

時,n-SAT也已被證明是NP-完全問題,幸運的是,

n=2

時,這個問題有多項式時間的解法。

我們把2-SAT轉化為

圖論

問題。假設原命題公式有

x_1,x_2,\dots,x_n

n

個命題變元,便一共有

2n

種可能的文字,對於每個文字,我們都用一個點表示。設

w_1

w_2

均為文字,如果

w_1\vee w_2

為真,則

\neg w_1\rightarrow w_2

\neg w_2\rightarrow w_1

均為真(其中

\rightarrow

蘊含

),於是從

\neg w_1

w_2

連一條有向邊,從

\neg w_2

w_1

連一條有向邊。

注意根據蘊含的性質,如果

a\rightarrow b\wedge b\rightarrow c

為真,則

a\rightarrow c

為真,所以在我們建出的圖中,

w_1

可達

w_2

意味著原公式可滿足時,

w_1\rightarrow w_2

應該為真。我們對每個命題變元進行如下考察:

如果

x_i

可達

\neg x_i

,則

x_i\rightarrow \neg x_i

\neg x_i \vee \neg x_i

\neg x_i

應為真,或者說

x_i

應為假

如果

\neg x_i

可達

x_i

,則

\neg x_i\rightarrow x_i

x_i\vee x_i

x_i

應為真

如果

x_i

\neg x_i

相互可達,那原命題公式是矛盾式

如果

x_i

\neg x_i

相互不可達,那

x_i

可以任意賦值

這當然可以用傳遞閉包

O(n^3)

地解決,但是還有更快的方法。我們先求一下強連通分量,如果某個

x_i

\neg x_i

屬於同一個強連通分量,那它們互相可達,肯定是矛盾式。然後對比

x_i

\neg x_i

(在縮點得到的DAG上)的拓撲序,把拓撲序較大的一個點設為真,因為如果它們之間單連通則一定是拓撲序較小的點可達拓撲序較大的點,如果它們之間不連通則怎樣賦值都無所謂。因為Tarjan演算法求強連通分量的過程中已經得到了拓撲序,我們不需要額外進行拓撲排序。複雜度是

O(n+m)

具體實現時,可以用

2i

號點表示

\neg x_i

2i+1

號點表示

x_i

。這樣要求

w_i

的對應點

\neg w_i

的編號只需要異或上1。

以下是洛谷模板題的主要程式碼:

int

n

m

cin

>>

n

>>

m

while

m

——

{

int

i

a

j

b

cin

>>

i

>>

a

>>

j

>>

b

edges

[(

2

*

i

+

a

^

1

]。

push_back

2

*

j

+

b

);

edges

[(

2

*

j

+

b

^

1

]。

push_back

2

*

i

+

a

);

}

for

int

i

=

2

i

<=

n

*

2

+

1

++

i

if

dfsn

i

])

tarjan

i

);

for

int

i

=

1

i

<=

n

++

i

{

if

scc

i

*

2

==

scc

i

*

2

+

1

])

return

cout

<<

“IMPOSSIBLE”

<<

endl

0

else

if

scc

i

*

2

>

scc

i

*

2

+

1

])

ans

i

=

1

else

ans

i

=

0

}

cout

<<

“POSSIBLE”

<<

endl

for

int

i

=

1

i

<=

n

++

i

cout

<<

ans

i

<<

“ ”

參考

^

對於某個命題公式,如果存在一組賦值使它為真,則稱它為可滿足式;否則為矛盾式。

^

a

b

c

命題變元或其否定稱為文字。若干個文字的析取稱為簡單析取式,例如a∨¬b∨c。簡單析取式的合取稱為合取正規化,例如(¬a∨c)∧b∧(a∨¬b∨c)。