SAT 是适定性 (Satisfiability) 问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 \(k>2\) 时该问题为 NP 完全的。所以我们只研究 \(k=2\) 的情况。

2-SAT,简单的说就是给出 \(n\) 个集合,每个集合有两个元素,已知若干个 \(<a,b>\),表示 \(a\) 与 \(b\) 矛盾(其中 \(a\) 与 \(b\) 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 \(n\) 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。

建图

我们将 \(n\) 个集合拆成两个点,分别代表着 truefalse.

a || b == true 时,我们可以得知:

a == false 时,b = true

b == false 时,a = true;

这两个关系存在因果关系;

a == true 时,我们不能得知 b = true 还是 b = false,这两者之间不存在因果关系,b == true 同理。

因此,我们将 \(a_0\) 向 \(b_1\) 连边,将 \(b_0\) 向 \(a_1\) 连边。

含义:

a == false 时,b = true

b == false 时,a = true

a && b == false 时,我们可以得知:

a == true 时,b = false;

b == true 时,a = false;

我们将 \(a_1\) 向 \(b_0\) 连边,将 \(b_1\) 向 \(a_0\) 连边。

含义:

a == true 时,b = false;

b == true 时,a = false

a && b == true 时,我们发现,a == trueb == true,除了这种情况不会再有其他情况了,即 \(a\) 的值一定为 true,\(b\) 的值一定为 true

这种情况下,我们将 \(a_0\) 向 \(a_1\) 连边,\(b_0\) 向 \(b_1\) 连边。

含义:

a == false 时,a = true;(即 \(a\) 一定不为 false

b == false 时,b = true。(即 \(b\) 一定不为 true)

判断是否有解

如果 \(a_0\) 可以到达 \(a_1\),说明 \(a\) 一定为 true

如果 \(a_1\) 可以到达 \(a_0\),说明 \(a\) 一定为 false

判断是否有解即在一种情况中 \(a\) 都有唯一确定的值,要么为 true,要么为 false,倘若在同一种情况中,\(a_0\) 可以到达 \(a_1\), \(a_1\) 可以到达 \(a_0\),则无法确定 \(a\) 的值,此情况下无解,即 \(a_0\) 与 \(a_1\) 在同一个强连通分量里。

用 tarjan 算法来找强连通分量即可。

题目

P4782 【模板】2-SAT 问题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll; const int N = 1e6 + 5; int n, m, tim, scc;
vector<int> son[N << 1], sta;
int dfn[N << 1], low[N << 1], lt[N << 1]; void tarjan(int u) {
dfn[u] = low[u] = ++ tim;
sta.push_back(u);
for (int v : son[u]) {
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]) {
lt[u] = ++ scc;
while (sta.back() != u) {
lt[sta.back()] = scc;
sta.pop_back();
}
sta.pop_back();
}
} int main() {
scanf("%d%d", &n, &m);
while (m --) {
int xi, i, xj, j;
scanf("%d%d%d%d", &xi, &i, &xj, &j);
son[xi + n * (i & 1)].push_back(xj + (j ^ 1) * n);
son[xj + n * (j & 1)].push_back(xi + (i ^ 1) * n);
}
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]) {
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for (int i = 1; i <= n; ++ i) {
printf("%d%c", (lt[i] < lt[i + n]), " \n"[i == n]);
}
return 0;
}

CF1475F

将 \(A\) 和 \(B\) 两个矩阵异或,得到一个新矩阵 \(C\),若 \(C\) 可以通过异或行或列的操作来变成全 \(0\) 矩阵,那么说明 \(A\) 可以通过异或得到 \(B\)。

我们发现,每一行或每一列要么不异或,要么异或一次,由此可以想到 2-SAT。

对于 \(C\) 中的元素,若 \(C(i, j)\) 为 \(1\),则要么行异或,要么列异或;若 \(C(i, j)\) 为 \(0\),则要么行和列都异或,要么行和列都不异或,由此建边判断是否有解。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll; const int N = 1005; int T, n, cnt, tim, scc;
int a[N][N], b[N][N], c[N][N], x[N][2], y[N][2];
int dfn[N << 2], low[N << 2], lt[N << 2];
vector<int> son[N << 2], Stack; void init() {
cnt = tim = scc = 0;
for (int i = 1; i <= n; ++ i) {
x[i][1] = ++ cnt;
son[cnt].clear();
dfn[cnt] = lt[cnt] = 0;
}
for (int i = 1; i <= n; ++ i) {
x[i][0] = ++ cnt;
son[cnt].clear();
dfn[cnt] = lt[cnt] = 0;
}
for (int i = 1; i <= n; ++ i) {
y[i][1] = ++ cnt;
son[cnt].clear();
dfn[cnt] = lt[cnt] = 0;
}
for (int i = 1; i <= n; ++ i) {
y[i][0] = ++ cnt;
son[cnt].clear();
dfn[cnt] = lt[cnt] = 0;
}
} void tarjan(int u) {
dfn[u] = low[u] = ++ tim;
Stack.push_back(u);
for (int v : son[u]) {
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]) {
lt[u] = ++ scc;
while (Stack.back() != u) {
lt[Stack.back()] = scc;
Stack.pop_back();
}
Stack.pop_back();
}
} int main() {
scanf("%d", &T);
while (T --) {
scanf("%d", &n);
init();
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
scanf("%1d", &a[i][j]);
}
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
scanf("%1d", &b[i][j]);
c[i][j] = a[i][j] ^ b[i][j];
if (c[i][j]) {
son[x[i][1]].push_back(y[j][0]);
son[y[j][0]].push_back(x[i][1]);
son[x[i][0]].push_back(y[j][1]);
son[y[j][1]].push_back(x[i][0]);
}
else {
son[x[i][1]].push_back(y[j][1]);
son[y[j][1]].push_back(x[i][1]);
son[x[i][0]].push_back(y[j][0]);
son[y[j][0]].push_back(x[i][0]);
}
}
}
for (int i = 1; i <= cnt; ++ i) {
if (! dfn[i]) {
tarjan(i);
}
}
int fg = 0;
for (int i = 1; i <= n; ++ i) {
if (lt[x[i][1]] == lt[x[i][0]]) {
puts("NO");
fg = 1;
break;
}
if (lt[y[i][1]] == lt[y[i][0]]) {
puts("NO");
fg = 1;
break;
}
}
if (! fg) puts("YES");
}
return 0;
}

「学习笔记」2-SAT问题的更多相关文章

  1. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  2. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  3. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  4. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  5. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  6. 「学习笔记」平衡树基础:Splay 和 Treap

    「学习笔记」平衡树基础:Splay 和 Treap 点击查看目录 目录 「学习笔记」平衡树基础:Splay 和 Treap 知识点 平衡树概述 Splay 旋转操作 Splay 操作 插入 \(x\) ...

  7. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  8. 「学习笔记」ST表

    问题引入 先让我们看一个简单的问题,有N个元素,Q次操作,每次操作需要求出一段区间内的最大/小值. 这就是著名的RMQ问题. RMQ问题的解法有很多,如线段树.单调队列(某些情况下).ST表等.这里主 ...

  9. 「学习笔记」递推 & 递归

    引入 假设我们想计算 \(f(x) = x!\).除了简单的 for 循环,我们也可以使用递归. 递归是什么意思呢?我们可以把 \(f(x)\) 用 \(f(x - 1)\) 表示,即 \(f(x) ...

  10. 「学习笔记」动态规划 I『初识DP』

    写在前面 注意:此文章仅供参考,如发现有误请及时告知. 更新日期:2018/3/16,2018/12/03 动态规划介绍 动态规划,简称DP(Dynamic Programming) 简介1 简介2 ...

随机推荐

  1. pip安装报错 cannot uninstall a distutils installed project

    sudo pip install --ignore-installed xxx 在安装jupyter notebook的时候,遇到了这个问题,于是上网搜索,搜到了靠谱答案github解决方案 sudo ...

  2. [扫描工具]dirsearch简单使用

    [扫描工具]dirsearch简单使用 dirsearch是一个python开发的目录扫描工具.和我们平时使用的dirb.御剑之类的工具一样,就是为了扫描网站的敏感文件和目录从而找到突破口. 安装: ...

  3. Windows10电源选项:睡眠、休眠、启用快速启动

    参考链接: http://www.dnpz.net/diannaozhishi/2223.html http://www.cfan.com.cn/2018/0118/130151.shtml 在介绍w ...

  4. 【其他】etcd

    配置 node1 name: etcd-1 data-dir: /data/etcd/node1 listen-client-urls: http://127.0.0.1:6701 advertise ...

  5. SpringBoot——常用配置

    application.yml配置信息 spring: profiles: active: dev application: name: jwt-token-security # Jackson 配置 ...

  6. [ACTF2020 新生赛]Include 1

    首先进入靶场可以看到trip 查看源码 点击进入提示我们能不能找到flag 可以看到这里是文件包含,想着包含index.php但是根目录是自动索引的,无论输入什么都是trip页面 又想着包含flag. ...

  7. 二叉树、B树、B*树、AVL树... 这么多树你真的搞清楚了吗?

    经常在面试或者平时工作中,我们都会听到类似的树,类似于二叉树.B树.B*树.AVL树等等,很多情况下可能对他们都是只有一知半解.今天我总结了所有常见的树的原理,深入浅出的分析了其中的优缺点和注意事项, ...

  8. 【问题解决】Nacos服务端NVDB-CNVDB-2023674205漏洞

    缘起 最近(2023.03.13)客户现场要求自检有无使用Nacos,原因是Nacos存在认证绕过高危漏洞,其漏洞代码NVDB-CNVDB-2023674205,本文就简单说一下这个事儿,以及如何解决 ...

  9. Android进度表示

    在连接上数据库之后,一切都变得简单了呢! 开心,很轻松地就能够将APP里面的相关内容写完啦! 尝试了好久的连接Mysql数据库,最后还是没有成功: 虽然Android studio里面自带的SQLit ...

  10. RTE2021 回顾丨实践中的摸爬滚打,AI OPS 落地之路

    本文整理自声网Agora SD - RTN 网络传输质量负责人于涛在 RTE2021 实时互联网大会上的演讲分享.他在演讲中针对传统 OPS 痛点.AI OPS 的优势以及 AI OPS 工程化的难点 ...