2-SAT

SAT是是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当\(k>2\)时该问题为 NP 完全的。

定义

\(n\) 个布尔变量 \(x_1\)\(\sim\)\(x_n\),另有 \(m\) 个需要满足的条件,每个条件的形式都是 「\(x_i\)true / false\(x_j\)true / false」。比如 「\(x_1\) 为真或 \(x_3\) 为假」、「\(x_7\) 为假或 \(x_2\) 为假」。

\[(x_i\or x_j) \and(\neg x_i\or x_j)\cdots \]

注意这里的或为排斥或

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

Tarjan SCC 缩点

把每个变量拆成两个点,\(X(True)\)\(X(False)\)。比如现在有一个要求\(X|Y=True\),则把\(X(False)\)\(Y(True)\)连一条边,把\(X(True)\)\(Y(False)\)连一条边。连出来的图是对称的,然后跑一遍 Tarjan,如果存在某个变量的两个点在同一个强连通分量中的情况,则无解,否则有解。

构造方案时,我们可以通过缩点后的拓扑序确定变量的值。如果变量\(x\)的拓扑序在\(\neg x\)之后,则取\(x\)为真,反之取\(x\)为假。注意 Tarjan 求得的 SCC 的编号相同与反拓扑序。

// luogu P4782
int32_t main() {
    int n, m, N;
    cin >> n >> m, N = n * 2 + 2;
    e.resize(N);
    dfn = inStk = low = scc = vector<int>(N);
    capacity.push_back(0);
    for (int i, a, j, b; m; m--) {
        cin >> i >> a >> j >> b;
        e[2 * i + (a ^ 1)].push_back(2 * j + b);
        e[2 * j + (b ^ 1)].push_back(2 * i + a);
    }
    for (int i = 2; i <= n * 2 + 1; i++)
        if (!dfn[i])
            tarjan(i);
    vector<int> res(n + 1);
    for (int i = 1; i <= n; i++) {
        if (scc[i * 2] == scc[i * 2 + 1])
            cout << "IMPOSSIBLE\n", exit(0);
        else if (scc[i * 2] > scc[i * 2 + 1])
            res[i] = 1;
    }
    cout << "POSSIBLE\n";
    for (int i = 1; i <= n; i++)
        cout << res[i] << " ";
    cout << "\n";
    return 0;
}
posted @ 2023-08-23 22:01  PHarr  阅读(22)  评论(0编辑  收藏  举报