浅谈2-SAT

前言

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

概念

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

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

  • \(P\)\(Q\) , 和 若非 \(P\) 则非 \(Q\)

    上述又称做逆否命题,待会讲解是会用到

  • 真切的例子:有你没我,有我没你!

在 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 \]

  • \(\mathfrak{A}\) 表示 \(a,b\) 两个事件中至少有一个发生
  • \(\mathfrak{B}\) 表示 \(a,b\) 两个事件同时发生

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

  • 建立 \(2N\) 个节点的有向图,每个变量 \(A_i\) 对应着两个节点,分别为 \(i\)\(i + N\)

    • 都是两个点的原因是一个事件存在正反两面,即上述我们所说的逆否命题,他的正面成立,自然反面也必然成立,若不能理解,请自行百度了解逆否命题,(此处的正面与反面非广义)
  • 当考虑条件时,让一对有关联的两个点建立有向边

    • 什么叫有关联呢,很简单,如果 \(A\) 成立,那么对应 \(B\) 的也一定成立,我们称此叫关联。最真切的例子就是,”今天你要登洛谷“,那么 “打卡” 这一事件 \(B\) 是你接下来一定要做的,类似的,都可以这么理解,存在必然性
  • 那么有向边指向呢? 存在关联的问题定有前后顺序,即只有先发生 \(A\), 才回先发生 \(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\)

转载请标明出处、

posted @ 2021-02-02 21:03  zxsoul  阅读(117)  评论(3编辑  收藏  举报