洛谷

洛谷题单指南-连通性问题-P4782 【模板】2-SAT

原题链接:https://www.luogu.com.cn/problem/P4782

题意解读:2-SAT模版题。

解题思路:

1、定义

2 - SAT问题是给定一个布尔公式,该公式由若干个子句组成,每个子句都是两个文字的析取(或关系),例如(a ∨ b)、(¬c ∨ d)等,其中a、b、c、d是布尔变量,¬表示取反。问题的目标是判断是否存在一种变量赋值方式,使得整个布尔公式为真。

2、算法思路

建立蕴含图:对于每个子句(a ∨ b),可以将其转化为两个蕴含关系¬a->b和¬b->a。然后,将每个变量x及其否定¬x看作图中的节点,将蕴含关系看作有向边,这样就可以得到一个蕴含图。对于节点x,其否定的节点可以设为x+n。

强连通分量分解:使用 Tarjan 算法等对蕴含图进行强连通分量分解。如果一个变量x和它的否定¬x在同一个强连通分量中,那么显然无法找到一种赋值使得整个公式成立,因为这意味着x既必须为真又必须为假,产生矛盾。编程时,枚举所有变量i,如果i和i+n的scc编号相同,说明产生矛盾。

拓扑排序与赋值:如果x和¬x不在同一个强连通分量中,那么可以对强连通分量进行拓扑排序。按照拓扑序的逆序进行赋值,对于每个强连通分量,如果其中的变量尚未赋值,则将该强连通分量中的所有变量赋值为真,同时将其对应的否定变量赋值为假。这样的赋值方式可以保证满足所有的子句。编程实现时,直接枚举所有的变量i,看i和i+n谁的拓扑序更大(也就是谁的scc编号更小),如果i拓扑序小i变量为true,如果i+n拓扑序小i变量为false。

3、时间复杂度

构建蕴含图的时间复杂度为O(m),其中m是子句的数量。Tarjan 算法求强连通分量的时间复杂度为O(n + m),其中n是变量的数量。所以整体时间复杂度为O(n + m)。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 2000005, M = 2000005;

int head[N], to[M], nxt[M], idx;
int dfn[N], low[N], timestamp;
stack<int> stk;
bool vis[N];
int scc[N], cnt;
bool ans[N];
int n, m;

void add(int a, int b)
{
    to[++idx] = b;
    nxt[idx] = head[a];
    head[a] = idx;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++timestamp;
    stk.push(u);
    vis[u] = true;
    for(int i = head[u]; i; i = nxt[i])
    {
        int v = to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v])
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(low[u] == dfn[u])
    {
        cnt++;
        while(true)
        {
            int t = stk.top(); stk.pop();
            scc[t] = cnt;
            vis[t] = false;
            if(t == u) break;
        }
    }
}

bool check()
{
    for(int i = 1; i <= n * 2; i++) //注意节点总数是2n
        if(!dfn[i])
            tarjan(i);

    for(int i = 1; i <= n; i++)
        if(scc[i] == scc[i + n]) //i和非i在同一个强联通分量
            return false;

    return true;
}

int main()
{
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1;i <= m; i++)
    {
        int x, a, y, b;
        cin >> x >> a >> y >> b;
        if(a == 1 && b == 1) // x || y
        {
            add(x + n, y); // !x -> y
            add(y + n, x); // !y -> x
        }
        else if(a == 1 && b == 0) // x || !y
        {
            add(x + n, y + n); // !x -> !y
            add(y, x); // y -> x
        }
        else if(a == 0 && b == 1) // !x || y
        {
            add(x, y); // x -> y
            add(y + n, x + n); // !y -> !x
        }
        else // !x || !y
        {
            add(x, y + n); // x -> !y
            add(y, x + n); // y -> !x
        }
    }

    if(!check()) cout << "IMPOSSIBLE" << endl;
    else 
    {
        cout << "POSSIBLE" << endl;
        for(int i = 1; i <= n; i++) 
            cout << (scc[i] < scc[i + n]) << " ";  //i比非i的拓扑序靠前则是1,否则是0
    }

    return 0;
}