C++
 Computer >> コンピューター >  >> プログラミング >> C++

2-SAT(2充足可能性)問題とは?C/C++での判定アルゴリズムを解説

2-SAT問題とは

2-SAT(2充足可能性)問題は、各節がちょうど2つのリテラルからなる論理式に対して、すべての節を真にするような変数の割り当て(充足割り当て)が存在するかどうかを判定する問題です。

対象となる論理式は次の形で表されます。

f = (x1 ∨ y1) ∧ (x2 ∨ y2) ∧ … ∧ (xn ∨ yn)

ここで各 xi、yi はブール変数またはその否定です。問題は「f は充足可能か?」という問いに帰着します。

含意への書き換え

鍵となるのは、選言(OR)を含意(→)で書き換えられるという同値関係です。次の3つの式はすべて同値です。

  • xi ∨ yi
  • ¬xi → yi
  • ¬yi → xi

そこで、各節 (xi ∨ yi) を上記の2つの含意文に変換します。

含意グラフの構築

次に、2n個の頂点を持つ有向グラフ(含意グラフ)を考えます。各節 (xi ∨ yi) に対して、次の2本の有向辺を追加します。

  • ¬xi から yi への辺
  • ¬yi から xi への辺

このグラフでは、「あるリテラルが偽なら、もう一方のリテラルは真でなければならない」という制約が辺として表現されています。

充足可能性の判定:SCCを利用

¬xi と xi が同じ強連結成分(SCC: Strongly Connected Component)に属する場合、f は充足不能と判定されます。これは「xi が真であると同時に偽でもある」という矛盾が生じることを意味します。

逆に、すべての変数について xi と ¬xi が異なるSCCに属していれば、f は充足可能です。

変数への値の割り当て

f が充足可能な場合、構築したグラフの縮約グラフ(SCCを頂点とするDAG)のトポロジカルソートを用いて、各変数に値を割り当てることができます。

  • トポロジカル順序で ¬xi が xi より後ろにある場合 → xi は FALSE とみなす
  • それ以外の場合 → xi は TRUE とみなす

この割り当てにより、すべての節が真になることが保証されます。

擬似コード

以下は、コサラジュ(Kosaraju)法でSCCを求め、2-SATの充足可能性を判定する擬似コードです。

func dfsFirst(vertex v):
    marked[v] = true
    for each vertex u adjacent to v do:
        if not marked[u]:
            dfsFirst(u)
    stack.push(v)

func dfsSecond(vertex v):
    marked[v] = true
    for each vertex u adjacent to v do:
        if not marked[u]:
            dfsSecond(u)
    component[v] = counter

// グラフの構築
for i = 1 to n do:
    addEdge(not x[i], y[i])
    addEdge(not y[i], x[i])

// 1回目のDFS(探索終了順をスタックに記録)
for i = 1 to n do:
    if not marked[x[i]]:
        dfsFirst(x[i])
    if not marked[y[i]]:
        dfsFirst(y[i])
    if not marked[not x[i]]:
        dfsFirst(not x[i])
    if not marked[not y[i]]:
        dfsFirst(not y[i])

set all marked values false
counter = 0
flip directions of edges // 辺 v -> u を u -> v に反転

// 逆グラフで2回目のDFS(SCCを検出)
while stack is not empty do:
    v = stack.pop
    if not marked[v]:
        counter = counter + 1
        dfsSecond(v)

// 充足可能性の判定
for i = 1 to n do:
    if component[x[i]] == component[not x[i]]:
        it is unsatisfiable
        exit
    if component[y[i]] == component[not y[i]]:
        it is unsatisfiable
        exit
it is satisfiable
exit

計算量

このアルゴリズムの計算量は、頂点数 V = 2n、辺数 E(節の数を m とすると E = 2m)として O(V + E)、すなわち変数の個数と節の個数に対して線形時間 O(n + m) となります。2-SATは多項式時間で解けることが知られている一方、節に3つ以上のリテラルを含む一般のSAT(3-SAT以上)はNP完全です。そのため、この線形時間アルゴリズムは競技プログラミングや実務の制約充足問題において非常に有用です。

  1. C++で棚の配置問題(Fitting Shelves Problem)を解くプログラムの作成方法

    この問題では、壁の長さを表す整数 W、および2種類の棚の長さを表す整数 n と m の3つの値が与えられます。課題は「棚の配置問題(Fitting Shelves Problem)」を解くプログラムを作成することです。 棚を配置した後に壁へ残る空きスペースを最小限に抑える方法を見つける必要があります。さらに副次的な条件として、大きな棚ほど製作コストの面で有利なため、できる限り大きな棚を優先的に使用しなければなりません。 出力は以下の形式で行います。 nサイズの棚の個数 mサイズの棚の個数 残りのスペース 問題の例 入力: W = 12, n = 5, m = 3 出力: 0 4 0 解説 こ

  2. C/C++で実装するバークレーアルゴリズム――分散システムの時刻同期を徹底解説

    バークレーアルゴリズムとは バークレーアルゴリズム(Berkeleys Algorithm)は、分散システムにおいて各ノードの時計を同期させるために用いられるアルゴリズムです。特に、以下のような状況にあるシステムで有効とされています。 マシンに正確な時刻源が存在しない場合 ネットワークやマシンにUTCサーバーが用意されていない場合 分散システムとは、物理的に離れた場所に配置された複数のノードが、ネットワークを介して相互に接続されたシステムのことを指します。各ノードの時計は独立して動作しているため、誤差が生じやすく、何らかの同期機構が必要になります。 バークレーアルゴリズムの仕組み このア