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. Error parsing HTTP request header 控制台报错分析与解决

    控制台报错信息: org.apache.coyote.http11.AbstractHttp11Processor process 信息: Error parsing HTTP request hea ...

  2. HTTP 协议相关

    一. HTTP常见请求头 1. Host (主机和端口号) 2. Connection (连接类型) 3.Upgrade-Insecure-Requests (升级为HTTPS请求) 4. User- ...

  3. IDEA设置自定义代码模板

    1. 进入IDEA界面,File–>Settings 注:其中, $END$代表打印字符串后光标所处的位置 如: System.out.println($END$); 表示输出后光标在()里面.

  4. STM32使用DMA接收不定长数据

    开启串口,是能串口全局中断 配置DMA并勾选Memory选项 继续配置工程并且生成代码 添加一些串口通讯使用的全局变量 #define BUFFER_SIZE 128 uint8_t Tx_Buf[5 ...

  5. 打印机出现错误0x00000709要如何解决

    就是微软2021年10月更新的这个补丁导致的 要卸载KB5006670. 原文:https://www.zhihu.com/question/298855357/answer/514515054 微软 ...

  6. TP5.1模板循环标签

    第一种volist name=assign中的变量名 id=数组中的key offset=开始循环的位置 length=步长 {volist name='list' id='vo' offset='0 ...

  7. appium 遇到连接设备状态是offline

    1.查看连接手机设备 adb derivces 时,手机状态是offline状态(无法正常连接). 解决法: 1.adb kill-server 终止adb调试服务 2.adb start-serve ...

  8. 给jui(dwz)的菜单树换一套漂亮的图标

    JUI是一个免费开源的框架,在使用初期,会遇到一些麻烦,因为文档实在太少了,完全不知道从哪里入门,但是,一旦你深入学习后,你会发现,你的选择是不错的,它会提高你开发的效率,同时,你会深深爱上它. 目前 ...

  9. Hugging Face 每周速递: Chatbot Hackathon;FLAN-T5 XL 微调;构建更安全的 LLM

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  10. DevOps|研发效能不是老板工程,是开发者服务

    有人说研发效能是老板工程.不是的,研发效能不是老板工程,它不直接服务于老板(虽然老板可能看一些报表),反而是服务于广大产研运(产品+研发+质量+运维)的同学,所以有的公司也把研发效能叫做基础中台,平台 ...