競技プログラミング用 C++ ライブラリ
graph/two_sat.hpp · GitHub で見る
共通ページ
#include "lib/graph/two_sat.hpp"
#include <cassert>
TwoSAT sat(3);
sat.add_clause(0, true, 1, false); // x[0] または !x[1]
sat.add_clause(1, true, 2, true);
assert(sat.satisfiable());
auto answer = sat.answer(); // std::vector<bool>
assert(answer[0] || !answer[1]);
assert(answer[1] || answer[2]);
sat.add_clause(0, false, 0, false);
assert(sat.satisfiable());
assert(!sat.answer()[0]);
TwoSAT(n = 0) で 0 始まりの n 個の bool 変数を作ります。
| 操作 | 内容・返り値 |
|---|---|
size() |
変数の個数を int で返す |
reserve(m) |
m 節分の含意辺の格納容量を予約する。返り値は void
|
add_clause(i,f,j,g) |
(x[i] == f) || (x[j] == g) を追加する。返り値は void
|
satisfiable() |
充足可能なら true、充足不可能なら false を bool で返す |
answer() |
直前に求めた充足代入のコピーを std::vector<bool> で返す |
f・g は bool 値です。answer() は、直前の satisfiable() が true で、以後に節を追加していない場合だけ呼べます。返す配列の長さは size()、各要素は対応する変数の値です。代入例は一意ではありません。
同じ変数を両辺に指定できます。add_clause(i,f,i,f) は変数の固定、add_clause(i,true,i,false) は恒真節です。恒真節では辺を追加せず、固定節では含意と対偶が同じ辺になるため 1 本だけ追加します。重複する節も指定できます。
変数数は 0 <= n <= INT_MAX / 2、i・j は [0,n)、実際に格納する含意辺は INT_MAX 以下である必要があります。予約する節数 m は std::size_t で、INT_MAX / 2 以下です。これらの範囲は assert の前提です。TwoSAT(0) は充足可能で、代入例は空配列です。節がなければ、全変数が false の代入例を返します。
充足可能・充足不可能のどちらの判定結果も保存します。節を追加せずに satisfiable() を再度呼ぶと、保存した結果を返します。add_clause は恒真節の場合も保存結果を無効にするため、追加後は再び satisfiable() を呼んでください。reserve は保存結果を無効にしません。コピーは節・保存結果とも独立します。
参考: Nyaan’s Library、maspypy library。
変数数を \(n\)、節数を \(m\) として、構築は \(O(n)\)、節追加は償却 \(O(1)\)、初回または節追加後の判定は \(O(n+m)\)、変更のない問題の再判定は \(O(1)\)、代入例のコピーは \(O(n)\)、保持・作業空間は \(O(n+m)\) です。空の問題は \(O(1)\) 時間・空間です。
#ifndef TWO_SAT_HPP
#define TWO_SAT_HPP
#include "../components/two_sat_core.hpp"
class TwoSAT {
int n_;
std::vector<std::pair<int, int>> edges_;
std::vector<int> forced_;
std::vector<bool> answer_;
bool valid_ = false, solved_ = false;
static int vertices(int n) {
assert(0 <= n && n <= std::numeric_limits<int>::max() / 2);
return n * 2;
}
public:
explicit TwoSAT(int n = 0) : n_(n), answer_(vertices(n) / 2) {}
int size() const { return n_; }
void reserve(std::size_t clauses) {
assert(clauses <= std::size_t(std::numeric_limits<int>::max() / 2));
edges_.reserve(clauses * 2);
}
void add_clause(int i, bool f, int j, bool g) {
assert(0 <= i && i < n_ && 0 <= j && j < n_);
valid_ = solved_ = false;
if (i == j && f != g) return;
assert(edges_.size() <= std::size_t(std::numeric_limits<int>::max() - 1 - int(i != j)));
edges_.emplace_back(2 * i + !f, 2 * j + g);
if (i == j) forced_.push_back(2 * i + f);
if (i != j) edges_.emplace_back(2 * j + !g, 2 * i + f);
}
bool satisfiable() {
if (solved_) return valid_;
valid_ = false;
if (edges_.empty()) std::fill(answer_.begin(), answer_.end(), false), valid_ = true;
else valid_ = two_sat_detail::solve(n_, edges_, forced_, answer_);
solved_ = true;
return valid_;
}
std::vector<bool> answer() const { assert(valid_); return answer_; }
};
#endif
#line 1 "graph/two_sat.hpp"
#line 1 "components/two_sat_core.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <limits>
#include <span>
#include <utility>
#include <vector>
namespace two_sat_detail {
inline bool solve(int variables, std::span<const std::pair<int, int>> edges, std::span<const int> forced, std::vector<bool> &answer) {
assert(0 <= variables && variables <= std::numeric_limits<int>::max() / 2);
assert(edges.size() <= std::size_t(std::numeric_limits<int>::max()) && answer.size() == std::size_t(variables));
int n = 2 * variables;
std::vector<signed char> values(variables, -1);
std::vector<int> trail;
trail.reserve(variables);
for (int literal : forced) {
if (values[literal >> 1] < 0) {
values[literal >> 1] = literal & 1;
trail.push_back(literal);
} else if (values[literal >> 1] != (literal & 1)) return false;
}
if (trail.size() == std::size_t(variables)) {
for (auto [from, to] : edges) {
if (values[from >> 1] == (from & 1) && values[to >> 1] != (to & 1)) return false;
}
for (int i = 0; i < variables; ++i) answer[i] = values[i];
return true;
}
std::vector<int> offset(std::size_t(n) + 1, -1), adjacency(edges.size());
std::size_t branching = 0;
for (std::size_t i = 0; i < edges.size(); ++i) {
auto [from, to] = edges[i];
branching += offset[from] >= 0;
adjacency[i] = offset[from];
offset[from] = static_cast<int>(i);
}
bool compact = false;
auto make_compact = [&] {
std::fill(offset.begin(), offset.end(), 0);
for (auto [from, to] : edges) ++offset[from];
for (int v = 1; v < n; ++v) offset[v] += offset[v - 1];
offset[n] = static_cast<int>(edges.size());
for (auto [from, to] : edges) adjacency[--offset[from]] = to;
compact = true;
};
if (variables >= 1024) {
auto closure = [&]<bool Adaptive>() -> int {
std::size_t budget = std::size_t(variables) + edges.size();
if constexpr (!Adaptive) budget = (std::min)(budget, std::numeric_limits<std::size_t>::max() / 2) * 2;
std::size_t switch_after = std::max<std::size_t>(1024, (std::size_t(variables) + edges.size()) / 16);
std::size_t compact_budget = budget - std::min(budget, switch_after);
auto propagate = [&] {
for (std::size_t i = 0; i < trail.size(); ++i) {
if (!budget) return -1;
--budget;
int v = trail[i];
if constexpr (Adaptive) if (!compact && budget < compact_budget) make_compact();
auto visit = [&](int to) {
if (!budget) return -1;
--budget;
if (values[to >> 1] < 0) {
values[to >> 1] = to & 1;
trail.push_back(to);
} else if (values[to >> 1] != (to & 1)) {
for (int literal : trail) values[literal >> 1] = -1;
return 0;
}
return 1;
};
if constexpr (Adaptive) if (compact) {
for (int at = offset[v]; at < offset[v + 1]; ++at) {
int status = visit(adjacency[at]);
if (status != 1) return status;
}
continue;
}
for (int at = offset[v]; at >= 0; at = adjacency[at]) {
int status = visit(edges[at].second);
if (status != 1) return status;
}
}
return 1;
};
auto empty = [&](int v) {
if constexpr (Adaptive) if (compact) return offset[v] == offset[v + 1];
return offset[v] < 0;
};
int status = propagate();
if (!status) return 0;
for (int i = 0; status > 0 && i < variables; ++i) if (values[i] < 0) {
if (empty(2 * i)) values[i] = 0;
else if (empty(2 * i + 1)) values[i] = 1;
else {
trail.assign(1, 2 * i);
values[i] = 0;
status = propagate();
if (!status) {
trail.assign(1, 2 * i + 1);
values[i] = 1;
status = propagate();
if (!status) return 0;
}
}
}
return status;
};
int status = edges.size() >= std::size_t(variables) && branching > edges.size() / 32
? closure.template operator()<true>() : closure.template operator()<false>();
if (status >= 0) {
if (status) for (int i = 0; i < variables; ++i) answer[i] = values[i];
return status;
}
}
if (!compact) make_compact();
trail.clear();
std::vector<int> order(n, -1);
struct Frame { int vertex, next, low; };
std::vector<Frame> stack;
stack.reserve(n);
int clock = 0, component = -2;
auto enter = [&](int v) {
order[v] = clock++;
trail.push_back(v);
stack.push_back({v, offset[v], order[v]});
};
for (int root = 0; root < n; ++root) if (order[root] == -1) {
if (offset[root] == offset[root + 1]) { order[root] = component--; continue; }
enter(root);
while (!stack.empty()) {
auto &frame = stack.back();
int v = frame.vertex, next = frame.next, low = frame.low;
for (; next < offset[v + 1]; ++next) {
int to = adjacency[next];
if (order[to] == -1) {
if (offset[to] == offset[to + 1]) { order[to] = component--; continue; }
break;
}
if (order[to] >= 0) low = std::min(low, order[to]);
}
frame.low = low;
if (next < offset[v + 1]) {
frame.next = next + 1;
enter(adjacency[next]);
} else {
stack.pop_back();
if (low == order[v]) {
int u;
do {
u = trail.back();
trail.pop_back();
order[u] = component;
if (order[u ^ 1] == component) return false;
} while (u != v);
--component;
}
if (!stack.empty()) stack.back().low = std::min(stack.back().low, low);
}
}
}
for (int i = 0; i < variables; ++i) answer[i] = order[2 * i] < order[2 * i + 1];
return true;
}
}
#line 5 "graph/two_sat.hpp"
class TwoSAT {
int n_;
std::vector<std::pair<int, int>> edges_;
std::vector<int> forced_;
std::vector<bool> answer_;
bool valid_ = false, solved_ = false;
static int vertices(int n) {
assert(0 <= n && n <= std::numeric_limits<int>::max() / 2);
return n * 2;
}
public:
explicit TwoSAT(int n = 0) : n_(n), answer_(vertices(n) / 2) {}
int size() const { return n_; }
void reserve(std::size_t clauses) {
assert(clauses <= std::size_t(std::numeric_limits<int>::max() / 2));
edges_.reserve(clauses * 2);
}
void add_clause(int i, bool f, int j, bool g) {
assert(0 <= i && i < n_ && 0 <= j && j < n_);
valid_ = solved_ = false;
if (i == j && f != g) return;
assert(edges_.size() <= std::size_t(std::numeric_limits<int>::max() - 1 - int(i != j)));
edges_.emplace_back(2 * i + !f, 2 * j + g);
if (i == j) forced_.push_back(2 * i + f);
if (i != j) edges_.emplace_back(2 * j + !g, 2 * i + f);
}
bool satisfiable() {
if (solved_) return valid_;
valid_ = false;
if (edges_.empty()) std::fill(answer_.begin(), answer_.end(), false), valid_ = true;
else valid_ = two_sat_detail::solve(n_, edges_, forced_, answer_);
solved_ = true;
return valid_;
}
std::vector<bool> answer() const { assert(valid_); return answer_; }
};