充足可能性問題とは?

充足可能性問題(satisfiability problem, SAT)は、一つの命題論理式が与えられたとき、それに含まれる変数の値を偽 (False) あるいは真 (True) にうまく定めることによって全体の値を真にできるか、という問題です。

真偽値をとる論理変数 x1, x2, x3, x4 … および論理演算子により論理式が構成されます。

◯論理否定 (¬x1)
 x1 が真ならば偽 偽ならば真
◯論理和 (x1 ∨ x2)
 x1 が真ならば x1、偽ならば x2
◯論理積 (x1 ∧ x2)
 x1 が真ならば x2、偽ならば x1
◯リテラル
 論理変数 (x1) またはその否定 (¬x1)
◯節
 リテラルの論理和 (x1 ∨ x2)

論理式全体の値を真にするような、真偽値 x1, x2, x3, x4 … の組み合わせは存在するかを問う充足可能性問題はNP完全ですが、論理積でつながれたひとつひとつの部分(節)に変数が二つしかない 2-SAT問題は節の数と変数の数に対する線形時間のアルゴリズムが存在します。節の長さが 3 以上の場合はNP完全に属します。

Implication グラフ

Implicationとは「包含」という意味であり、論理で言えば論理包含がこれにあたります。命題P, Qに対しての論理包含を P ⇒ Q 等と書き、「P が偽または Q が真」という意味を表します。

(P ⇒ Q) ⇔ (¬P ∨ Q)
(¬P ⇒ Q) ⇔ (P ∨ Q)
(¬P ⇒ Q) ⇔ (P ∨ Q) ⇔ (Q ∨ P) ⇔ (¬Q ⇒ P)
(P ∨ Q) ⇔ (¬P ⇒ Q) ∧ (¬Q ⇒ P)

入力の論理式の論理和を論理包含によって書きなおすことができます。このグラフの頂点集合は変数とその否定であり、辺は論理包含の関係を表します。このグラフでは u ⇒ v の場合にだけ頂点 u, v 間に有向辺が存在します。

このグラフの強連結成分について考えてみましょう。強連結成分であるということはその集合内の任意の2頂点間に有向道が存在するということです。

x から y への有向道が存在するということは強連結成分の定義から y から x への有向道も存在することを意味します。 x ⇒ y なら y ⇒ x ということになります。

もし x と y がある変数の真偽値とその否定の真偽値であった場合、どうなるでしょうか? これは変数の真偽値の関係に矛盾が生じているということを意味します。

Implication グラフを強連結成分分解した結果、ある変数とその否定が同じ強連結成分に属していた場合、与えられた論理式は充足できないということになります。これによって充足可能性問題を解くことができます。

強連結成分分解をして充足可能性問題を解いてみる

H – Two SAT

H – Two SAT

1 から N までの番号のついた N 本の旗があります。これらの旗を数直線状に設置します。

旗 i は座標 X_i または 座標 Y_i に設置することができます。ただしどの 2 つの旗についても,その間の距離が D 以上である必要があります。

N 本の旗を設置することが可能かどうか判定し、可能であるならば実際に置き方を 1 つ示してください。

入力されたデータ
N D
X_1 Y_1
X_2 Y_2

X_N Y_N
(ただし、1 ≦ N ≦ 1000, 0 ≦ D ≦ 10^9
0 ≦ X i < Y i ≦ 10^9 )

各旗は「座標 X_i に設置する」「座標 X_i に設置しない (Yi に設置する)」という 2 通りずつの選択肢があります。そこで x_i を 旗iを「座標 X_i に設置するかどうか?」と定義します。

すると旗 a とそれ以外の旗 b の関係から x_a は以下のようになります。

|X_a – X_b| < D のとき、「x_a == false または x_b == false」でなければならない
|X_a – Y_b| < D のとき、「x_a == false または x_b == true」でなければならない
|Y_a – X_b| < D のとき、「x_a == true または x_b == false」でなければならない
|Y_a – Y_b| < D のとき、「x_a == true または x_b == true」でなければならない

もし以下のような入力が与えられた場合、どうなるかを考えてみます。

旗 0 は 1 4
旗 1 は 2 5
旗 2 は 0 6
2 以上離すこと

|X_0 – X_1| < 2 なので x_0 == false ∨ x_1 == false
|Y_0 – Y_1| < 2 なので x_0 == true ∨ x_1 == true
|X_0 – X_2| < 2 なので x_0 == false ∨ x_2 == false
|Y_1 – Y_2| < 2 なので x_1 == true ∨ x_2 == true

(P ∨ Q) ⇔ (¬P ⇒ Q) ∧ (¬Q ⇒ P) だったので ⇒ を用いて表現すると以下のようになります。

x_0 == false ∨ x_1 == false なので x_0 ⇒ ¬x_1, x_1 ⇒ ¬x_0
x_0 == true ∨ x_1 == true なので ¬x_0 ⇒ x_1, ¬x_1 ⇒ x_0
x_0 == false ∨ x_2 == false なので x_0 ⇒ ¬x_2, x_2 ⇒ ¬x_0
x_1 == true ∨ x_2 == true なので ¬x_1 ⇒ x_2, ¬x_2 ⇒ x_1

xi と ¬xi の頂点を生成して ⇒ で辺を張り、強連結成分分解をしたときに、同じ強連結成分のなかに xi と ¬xi が存在しなければ旗を設置することが可能ということになります。

ところで同じ強連結成分のなかに x_i と ¬x_i が存在しない場合、x_i は true か false のどちらなのかを確定させることができるのですが、どのようにして確定させればよいのでしょうか?

強連結成分分解は深さ優先探索をして各頂点に通し番号の ID をつけ、辺を逆にして大きいものから再度深さ優先探索をするという処理をおこないます。このとき先に確定した強連結成分に番号をつけていきます。x_i に相当する頂点の番号のほうが ¬x_i に相当する頂点の番号よりも大きい場合 xi は true です。

実装してみる

Vertex クラス、Dfs1 メソッド、Dfs2 メソッドは 強連結成分分解 蟻本読書会と同じなので省略しています。

x_i に対応する頂点とこの論理否定に該当する頂点が必要なので全部で N * 2 個の頂点が必要です。ここでは x_i に対応する頂点の頂点番号を i とし、この論理否定に該当する頂点番号を i + N とします。

Implication グラフを生成することができたらあとは強連結成分分解をして求解するだけです。

TwoSAT クラスを定義する

こういう難しい処理は「さあ書け」と言われてすぐにできる自信がありません。2 SAT を解くためのクラスを定義して使い回せるようにします。

このクラスを用いて問題を解くならこうなります。

TwoSAT クラスを用いて問題を解いてみる

No.274 The Wall

No.274 The Wall

1×1 のブロックが横に M 個連なった 1×M のブロックがある。
ブロックは基本白色であるが左から L 番目から左から R 番目(0-index)までの連続したブロックはピンク色である。ブロックは180度回転することもできる。

横の長さが同じ 1×M の N 本のブロックが与えられる。
それぞれのブロックはそれぞれピンク色の連続した領域を1つ持つ。
N 本のブロックを上に積んでいき良い壁を作りたい。
良い壁とは縦の列でブロックを見たときピンク色のブロックが 1 つ以下のものをいう。

以下のようなブロックが与えられたとする。

これをそのまま積み上げたものがこれであるが、これは「良い壁」ではない。一番右の列にピンク色のブロックがふたつある。

上から2段目のものを 180 度反転した。これは「良い壁」である。

与えられる N 本のブロックをすべて積んで良い壁を作れるか判定せよ。

入力されるデータ
N M
L_0 R_0
L_1 R_1

L_N-1 R_N-1

(ただし、
2 ≦ N ≦ 2000
2 ≦ M ≦ 4000
L_i 一番左のピンク色のブロックの位置
R_i 一番右のピンク色のブロックの位置)
0 ≦ L_i ≦ R_i ≦ M )

これはブロックをそのまま置くか180度回転させて置くかの組み合わせで「良い壁」を作ることができるかという充足可能性問題です。

a < b としてすべての a, b の組み合わせを考えてみることにします。x は a をそのまま置くとき true, 180 度反転させて置く場合 false です。y は b をそのまま置くかどうかです。

(1)a と b がそのままだと衝突しないし、b を 180 度回転しても衝突しない場合
この 2 つは絶対に干渉しないのでなにもしない。
(2)a と b がそのままだと衝突しないが、b を 180 度回転すると衝突する場合
両方ともそのままか両方とも 180 度回転させないといけないので x ⇒ y, ¬x ⇒ y
(3)a と b がそのままだと衝突するが、b を 180 度回転すると衝突しない場合
片方だけ 180 度回転させないといけないので ¬x ⇒ y, x ⇒ ¬y
(4)a と b がそのままだと衝突し、b を 180 度回転しても衝突する場合
どうやっても「悪い壁」しか作れないので “No”を出力する

この場合は ∨ ではなく ⇒ を使ったほうがわかりやすいです。