算法学习笔记:2-SAT
SAT 是适定性(Satisfiability)问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 \(k>2\) 时该问题为 NP 完全的。所以我们只研究 \(k=2\) 的情况。
定义
2-SAT,简单的说就是给出 \(n\) 个集合,每个集合有两个元素,已知若干个 \(<a,b>\) ,表示 \(a\) 与 \(b\) 矛盾(其中 \(a\) 与 \(b\) 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 \(n\) 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。
现实意义
比如邀请人来吃喜酒,夫妻二人必须去一个,然而某些人之间有矛盾(比如 A 先生与 B 女士有矛盾,C 女士不想和 D 先生在一起),那么我们要确定能否避免来人之间没有矛盾,有时需要方案。这是一类生活中常见的问题。
使用布尔方程表示上述问题。设 \(a\) 表示 A 先生去参加,那么 B 女士就不能参加( \(\neg a\) ); \(b\) 表示 C 女士参加,那么 \(\neg b\) 也一定成立(D 先生不参加)。总结一下,即 \((a \vee b)\) (变量 \(a, b\) 至少满足一个)。对这些变量关系建有向图,则有: \(\neg a\Rightarrow b\wedge\neg b\Rightarrow a\) ( \(a\) 不成立则 \(b\) 一定成立;同理, \(b\) 不成立则 \(a\) 一定成立)。建图之后,我们就可以使用缩点算法来求解 2-SAT 问题了。
常用解决方法
Tarjan SCC 缩点
算法考究在建图这点,我们举个例子来讲:
假设有 \({a1,a2}\) 和 \({b1,b2}\) 两对,已知 \(a1\) 和 \(b2\) 间有矛盾,于是为了方案自洽,由于两者中必须选一个,所以我们就要拉两条条有向边 \((a1,b1)\) 和 \((b2,a2)\) 表示选了 \(a1\) 则必须选 \(b1\) ,选了 \(b2\) 则必须选 \(a2\) 才能够自洽。
然后通过这样子建边我们跑一遍 Tarjan SCC 判断是否有一个集合中的两个元素在同一个 SCC 中,若有则输出不可能,否则输出方案。构造方案只需要把几个不矛盾的 SCC 拼起来就好了。
输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果变量 \(\neg x\) 的拓扑序在 \(x\) 之后,那么取 \(x\) 值为真。应用到 Tarjan 算法的缩点,即 \(x\) 所在 SCC 编号在 \(\neg x\) 之前时,取 \(x\) 为真。因为 Tarjan 算法求强连通分量时使用了栈,所以 Tarjan 求得的 SCC 编号相当于反拓扑序。
显然地,时间复杂度为 \(O(n+m)\) 。
爆搜
就是沿着图上一条路径,如果一个点被选择了,那么这条路径以后的点都将被选择,那么,出现不可行的情况就是,存在一个集合中两者都被选择了。
那么,我们只需要枚举一下就可以了,数据不大,答案总是可以出来的。
爆搜模板
下方代码来自刘汝佳的白书:
// 来源:白书第 323 页
struct Twosat {
int n;
vector<int> g[maxn * 2];
bool mark[maxn * 2];
int s[maxn * 2], c;
bool dfs(int x) {
if (mark[x ^ 1]) return false;
if (mark[x]) return true;
mark[x] = true;
s[c++] = x;
for (int i = 0; i < (int)g[x].size(); i++)
if (!dfs(g[x][i])) return false;
return true;
}
void init(int n) {
this->n = n;
for (int i = 0; i < n * 2; i++) g[i].clear();
memset(mark, 0, sizeof(mark));
}
void add_clause(int x, int y) { // 这个函数随题意变化
g[x].push_back(y ^ 1); // 选了 x 就必须选 y^1
g[y].push_back(x ^ 1);
}
bool solve() {
for (int i = 0; i < n * 2; i += 2)
if (!mark[i] && !mark[i + 1]) {
c = 0;
if (!dfs(i)) {
while (c > 0) mark[s[--c]] = false;
if (!dfs(i + 1)) return false;
}
}
return true;
}
};
例题
**HDU3062 Party **
题面:有 n 对夫妻被邀请参加一个聚会,因为场地的问题,每对夫妻中只有 \(1\) 人可以列席。在 \(2n\) 个人中,某些人之间有着很大的矛盾(当然夫妻之间是没有矛盾的),有矛盾的 \(2\) 个人是不会同时出现在聚会上的。有没有可能会有 \(n\) 个人同时列席?
这是一道多校题,裸的 2-SAT 判断是否有方案,按照我们上面的分析,如果 \(a1\) 中的丈夫和 \(a2\) 中的妻子不合,我们就把 \(a1\) 中的丈夫和 \(a2\) 中的丈夫连边,把 \(a2\) 中的妻子和 \(a1\) 中的妻子连边,然后缩点染色判断即可。
#include<bits/stdc++.h>
#define maxn 2018
#define maxm 4000400
using namespace std;
int Index, instack[maxn], DFN[maxn], LOW[maxn];
int tot, color[maxn];
int numedge, head[maxn];
struct Edge {
int nxt, to;
} edge[maxm];
int sta[maxn], top;
int n, m;
void add(int x, int y) {
edge[++numedge].to = y;
edge[numedge].nxt = head[x];
head[x] = numedge;
}
void tarjan(int x) { // 缩点看不懂请移步强连通分量上面有一个链接可以点。
sta[++top] = x;
instack[x] = 1;
DFN[x] = LOW[x] = ++Index;
for (int i = head[x]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (!DFN[v]) {
tarjan(v);
LOW[x] = min(LOW[x], LOW[v]);
} else if (instack[v])
LOW[x] = min(LOW[x], DFN[v]);
}
if (DFN[x] == LOW[x]) {
tot++;
do {
color[sta[top]] = tot; // 染色
instack[sta[top]] = 0;
} while (sta[top--] != x);
}
}
bool solve() {
for (int i = 0; i < 2 * n; i++)
if (!DFN[i]) tarjan(i);
for (int i = 0; i < 2 * n; i += 2)
if (color[i] == color[i + 1]) return 0;
return 1;
}
void init() {
top = 0;
tot = 0;
Index = 0;
numedge = 0;
memset(sta, 0, sizeof(sta));
memset(DFN, 0, sizeof(DFN));
memset(instack, 0, sizeof(instack));
memset(LOW, 0, sizeof(LOW));
memset(color, 0, sizeof(color));
memset(head, 0, sizeof(head));
}
int main() {
while (~scanf("%d%d", &n, &m)) {
init();
for (int i = 1; i <= m; i++) {
int a1, a2, c1, c2;
scanf("%d%d%d%d", &a1, &a2, &c1, &c2); // 自己做的时候别用 cin 会被卡
add(2 * a1 + c1,
2 * a2 + 1 - c2); // 我们将 2i+1 表示为第 i 对中的,2i 表示为妻子。
add(2 * a2 + c2, 2 * a1 + 1 - c1);
}
if (solve())
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
练习题
HDU1814 和平委员会
POJ3683 牧师忙碌日
其它
文章开源在 Github - blog-articles,点击 Watch 即可订阅本博客。 若文章有错误,请在 Issues 中提出,我会及时回复,谢谢。
如果您觉得文章不错,或者在生活和工作中帮助到了您,不妨给个 Star,谢谢。
(文章完)
算法学习笔记:2-SAT的更多相关文章
- C / C++算法学习笔记(8)-SHELL排序
原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...
- Manacher算法学习笔记 | LeetCode#5
Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...
- Johnson算法学习笔记
\(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...
- 某科学的PID算法学习笔记
最近,在某社团的要求下,自学了PID算法.学完后,深切地感受到PID算法之强大.PID算法应用广泛,比如加热器.平衡车.无人机等等,是自动控制理论中比较容易理解但十分重要的算法. 下面是博主学习过程中 ...
- Johnson 全源最短路径算法学习笔记
Johnson 全源最短路径算法学习笔记 如果你希望得到带互动的极简文字体验,请点这里 我们来学习johnson Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法.它允许一些 ...
- 算法学习笔记——sort 和 qsort 提供的快速排序
这里存放的是笔者在学习算法和数据结构时相关的学习笔记,记录了笔者通过网络和书籍资料中学习到的知识点和技巧,在供自己学习和反思的同时为有需要的人提供一定的思路和帮助. 从排序开始 基本的排序算法包括冒泡 ...
- R语言实现关联规则与推荐算法(学习笔记)
R语言实现关联规则 笔者前言:以前在网上遇到很多很好的关联规则的案例,最近看到一个更好的,于是便学习一下,写个学习笔记. 1 1 0 0 2 1 1 0 0 3 1 1 0 1 4 0 0 0 0 5 ...
- 二次剩余Cipolla算法学习笔记
对于同余式 \[x^2 \equiv n \pmod p\] 若对于给定的\(n, P\),存在\(x\)满足上面的式子,则乘\(n\)在模\(p\)意义下是二次剩余,否则为非二次剩余 我们需要计算的 ...
- SPFA算法学习笔记
一.理论准备 为了学习网络流,先水一道spfa. SPFA算法是1994年西南交通大学段凡丁提出,只要最短路径存在,SPFA算法必定能求出最小值,SPFA对Bellman-Ford算法优化的关键之处在 ...
- 算法学习笔记(三) 最短路 Dijkstra 和 Floyd 算法
图论中一个经典问题就是求最短路.最为基础和最为经典的算法莫过于 Dijkstra 和 Floyd 算法,一个是贪心算法,一个是动态规划.这也是算法中的两大经典代表.用一个简单图在纸上一步一步演算,也是 ...
随机推荐
- Django之 Views组件
本节内容 路由系统 models模型 admin views视图 template模板 我们已经学过了基本的view写法 单纯返回字符串 1 2 3 4 5 6 7 8 def current_dat ...
- [USACO3.1]形成的区域(扫描线+离散化)
[USACO3.1]形成的区域(P6432) 日期:2020-05-31 目录 [USACO3.1]形成的区域(P6432) 一.题意分析 二.算法分析 1. 暴力 0). 初始状态(红点为原点) 1 ...
- MVC + EFCore 项目实战 - 数仓管理系统4 – 需求分解
上次课程我们完成了项目基本的UI风格配置. 现在就开始进入我们的需求开发,我们先捋一下需求. 一.总体需求说明 项目背景第一篇文章已有介绍,我们回顾一下. 这是一个数据管理"工具类" ...
- This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary lo的解决办法
创建存储过程时,出错信息: ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA ...
- echarts 实战 : 恼人的间隔问题
使用 echarts 的时候,可能我们需要这个图表的间隔是固定的.比如 3个 4个 5个. (注意计算间隔数量的时候是不算 x轴 本身的.) 这个问题看似简单,其实有点麻烦. yAxis.splitN ...
- 图灵学院笔记-java虚拟机底层原理
Table of Contents generated with DocToc 一.java虚拟机概述 二.栈内存解析 2.1 概述 2.2 栈帧内部结构 2.2.1 我们来解析一下compute() ...
- DNS反向查询
DNS反向查询是什么 DNS反向查询大概的一个定义就是: 从 IP 地址获取 PTR 记录.也就是说,通过使用一些网络工具可以将 IP 地址转换为主机名. 实际上,PRT 代表 POINTER,在 D ...
- 修改ElementUI样式的几种方式
ElementUI是一款非常强大的前端UI组件库,它默认定义了很多美观的样式,但是我们在实际开发过程中不可避免地遇到需要修改ElementUI默认样式.下面总结了几种修改默认样式的方法. 1. 新建全 ...
- [spring] -- 设计模式篇
工厂模式 Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象. BeanFactory :延迟注入(使用到某个 bean 的时候才 ...
- MVC + EFCore 项目实战 - 数仓管理系统7 - 数据源管理中--新增数据源
上篇我们完成了数据源列表展示功能(还未测试). 本篇我们来新增数据源,并查看列表展示功能. 接上篇: 二.数据源管理功能开发 2.新增数据源 我们用模态对话框来完成数据源的新增,效果如下图: 我们 ...