2-SAT

作用

\(n\) 个布尔变量,有 \(m\) 个限制形如:\(i\) 变量为真,则 \(j\) 变量为假、\(i\) 变量为真,则 \(j\) 变量为真……

求出每个布尔变量的值,使得满足所有限制,或者报告无解。

做法

形如上述的限制,可以建一条 \(i\to j\) 的边,表示一个限制。

但是一个变量可以有 \(0\)\(1\) 两种值,所以不妨对一个变量 \(i\) 拆成 \(i\)\(i+n\) 两个点,分别表示取 \(0\) 和取 \(1\)

建完边后,直接说结论:

一、对于一个变量 \(i\),如果图中有一条 \(i\to i+n\) 的路径,而没有 \(i+n\to i\) 的路径,那么变量 \(i\)\(0\) 就不合法,而取 \(1\) 就合法,反之。如果 \(i\to i+n\)\(i+n\to i\) 的路径都没有,任意取一个即可。

二、对于一个变量 \(i\),如果图中同时存在 \(i\to i+n\)\(i+n\to i\) 这两条路径,则无解。

知道这个后跑一个 Tarjan,求出每个点所属的强连通编号,记做 \(id\)

把上述两条结论用代码翻译就是:

一、对于变量 \(i\),如果 \(id_i\lt id_{i+n}\),那么变量 \(i\)\(0\) 合法,取 \(1\) 不合法,反之。在 Tarjan 中,强连通编号小的点是一定不能到大强连通大的点的。

二、对于变量 \(i\),如果 \(id_i=id_{i+n}\) 那么无解。

细节

2-SAT 只能做 \(A\)\(B\) 的限制,如果要做 \(A\)\(B\) 的限制,就需要处理一下。

例如,\(i\) 真或 \(j\) 假这条限制,可以变成 \(i\) 假则 \(j\) 假、\(j\) 真则 \(i\) 真这两条限制。

例题

#include <bits/stdc++.h>

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 2e6 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;

int n, m;
vector< int> G[N], sta;

int tot, cnt;
int dfn[N], low[N], vis[N], col[N];

void tarjan( int u) {
    sta.push_back(u), vis[u] = 1;
    dfn[u] = low[u] = ++ tot;
    
    for ( auto v : G[u]) {
        if (! dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
        else if (vis[v]) low[u] = min(low[u], dfn[v]);
    }
    
    if (dfn[u] != low[u]) return ;
    
    int v; ++ col[0];
    
    do {
        v = sta.back(); sta.pop_back(), vis[v] = 0;
        col[v] = col[0];
    } while (v != u) ;
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;

    while (m --) {
        int i, a, j, b;
        cin >> i >> a >> j >> b;

        if (a && b) G[i].push_back(j + n), G[j].push_back(i + n);
        if (! a && ! b) G[i + n].push_back(j), G[j + n].push_back(i);
        if (! a && b) G[i + n].push_back(j + n), G[j].push_back(i);
        if (a && ! b) G[i].push_back(j), G[j + n].push_back(i + n);
    }

    for ( int i = 1; i <= 2 * n; i ++)
        if (! dfn[i]) tarjan(i);

    for ( int i = 1; i <= n; i ++)
        if (col[i] == col[i + n]) cout << "IMPOSSIBLE\n", exit(0);

    cout << "POSSIBLE\n";

    for ( int i = 1; i <= n; i ++)
        if (col[i] < col[i + n]) cout << "0 ";
        else cout << "1 ";

    return 0;
}
posted @ 2025-08-14 09:40  咚咚的锵  阅读(4)  评论(0)    收藏  举报