题解 P4782 【【模板】2-SAT 问题】

· · 题解

浅谈2-SAT

前言

最近也是刚刷到,学了两个小时,分享一下我对它的理解

概念

2-SAT 拆开可以从字面上大体了解,即:满足条件数量为2的情况下得到的可行性最优解。

它的对象一般都是我们高中学的逆否命题,例如:

在 2-SAT 问题中,我们总能把 M 统一转化为 “若变量 A_i 赋值为 A_i,p ,则变量 A_j 就必须赋值为 A_j,q, 其中 p,q 均为我们常用的true,false ”

建边

回想一下高中概率统计那一部分,我们学过概率的计算,那么它表示什么

\mathfrak{A}\ P(a+b)=a\lor b \mathfrak{B}\ b.\ P(ab)=a\land b

有了数学的理解,我们的建边会有新的台阶

实现

Tarjan 算法是个不错的选择,因为它可以寻找请连通分量,强连通分量里存在我们需要的可行解。

无解: 如果一个事件的正反面都在同一个强联通分量里的,即你吃了苹果,可你又没吃苹果,这很冤,所以这是矛盾的,自然不存在可行解.

时间复杂度

保证所有的 2N 点都要被 tarjan 所以为:

O(N+M)

可行解选择策略

我比较喜欢用拓扑序,即我们在tarjan时用来标记强连通分量的数组

因为 tarjan 的本质就是深搜,所以拓扑序按点从小到大排的话,拓扑序是由大到小的,即搜索树越深的点,拓扑序越小

因此我们可以通过比较拓扑序得到可行解,不同的题对于大小的不一样,

我认为,注意是我认为,如果否命题可以连向真命题,那么拓扑序大的记录答案,反之记录小的。讲解完毕!

Code

#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;

const int A = 1e5 + 11;
const int B = 5 *1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}
vector<int> e[B];
int vis[B];
int mon[B], cnt, n, m, low[B], dfn[B], lt[B], sum, ans, dis[B], st[B], top;
int x[B], y[B], vx[B], s, p;
void tarjan(int u){
  low[u] = dfn[u] = ++cnt, st[++top] = u;
  for (int i = 0; i < e[u].size(); i++){
    int v = e[u][i];
    if(!dfn[v]){ tarjan(v); low[u] = min(low[u], low[v]);}
    else if(!lt[v]) low[u] = min(low[u], dfn[v]);
  }
  if(dfn[u] == low[u]){
    sum++; 
    while(st[top] != u) {lt[st[top--]] = sum;}
    lt[st[top--]] = sum;
  }
}
int main() {
// freopen("a.in", "r", stdin);
//  freopen(".out", "w", stdout);
  n = read(), m = read();
  for(int i = 1; i <= m; i++){
     int x, a, y, b;
     cin >> x >> a >> y >>b;
     e[x + n * (a ^ 1)].push_back(y + n * (b & 1));
     e[y + n * (b ^ 1)].push_back(x + n * (a & 1));  
  }
  for(int i = 1; i <= (n << 1); i++) if(!dfn[i]) tarjan(i);
  for(int i = 1; i <= n; i++)
       if(lt[i] == lt[i + n]) 
        {
            cout << "IMPOSSIBLE" << "\n";
            return 0;
        }
  cout << "POSSIBLE" << endl;
  for(int i = 1; i <= n; i++) printf("%d ", lt[i] > lt[i + n]);
  return 0;
}

作者 @Tzy

转载请标明出处、