目录

    讲课:2-SAT

    讲课:\(2\mathrm{-SAT}\)

    命题逻辑基础

    这是大二上学期课程《离散数学》的部分内容。考虑到 2025 级同学可能不了解相关概念,稍微进行一些补充。

    命题逻辑的基本概念

    命题与联结词

    非真即假的陈述句称作命题

    作为命题的陈述句所表达的判断结果称作命题的真值,真值只取两个值:。真值为真的称作真命题,真值为假的命题称作假命题。任何命题的真值都是唯一的。既不能为真也不能为假的陈述句称作悖论,悖论不是命题。

    不能被分解成更简单的命题称作简单/原子命题,由简单命题通过联结词联结而成的命题称作复合命题

    定义 1.1\(p\) 为命题,复合命题“非 \(p\)”/“\(p\) 的否定”称作 \(p\)否定式,记作 \(\neg p\) 。符号 \(\neg\) 称作否定联结词。规定 \(\neg p\) 为真当且仅当 \(p\) 为假。

    定义 1.2\(p,q\) 为两个命题,复合命题\(p\) 并且 \(q\)”/“\(p\)\(q\)称为 \(p\)\(q\)合取式,记作 \(p \land q\)\(\land\) 称作合取联结词。规定 \(p \land q\) 为真当且仅当 \(p\)\(q\) 同时为真。

    定义 1.3\(p,q\) 为两个命题,复合命题“\(p\)\(q\)”称为 \(p\)\(q\)析取式,记作 \(p \lor q\)\(\lor\) 称作析取联结词。规定 \(p \lor q\) 为假当且仅当 \(p\)\(q\) 同时为假。

    定义 1.4\(p,q\) 为两个命题,复合命题“如果 \(p\)\(q\) ”称为 \(p\)\(q\)蕴含式,记作 \(p \rightarrow q\) ,并称 \(p\) 是蕴含式的前件\(q\) 是蕴含式的后件\(\rightarrow\) 称作蕴含联结词。并规定 \(p \rightarrow q\) 为假当且仅当 \(p\) 为真 \(q\) 为假。

    定义 1.5\(p,q\) 为两个命题,复合命题“\(p\) 当且仅当 \(q\)”称作 \(p\)\(q\) 的等价式,记作 \(p \leftrightarrow q\)\(\leftrightarrow\) 称作等价联结词。规定 \(p \leftrightarrow q\) 为真当且仅当 \(p\)\(q\) 同时为真或同时为假。

    命题公式及其赋值

    简单命题是命题逻辑中最基本的研究单位,其真值是确定的,又称作命题常项/元。取值 \(1\)\(0\) 的变元称作命题变项/元。可以用命题变项表示真值可以变化的陈述句,命题变项不是命题。

    将命题变项用联结词和圆括号按照一定的逻辑关系联结起来的符号串称作(合式)公/命题形。当使用联结词集 \(\{\neg,\land,\lor,\rightarrow,\leftrightarrow\}\) 时,合式公式定义如下。

    定义 1.6

    1. 单个命题变项和命题常项是合式公式,并称为原子命题公式。
    2. \(A,B\) 是合式公式,则 \(\neg A, (A \land B), (A \lor B), (A \rightarrow B), (A \leftrightarrow B)\) 是合式公式。

    \(A,B\) 为合式公式,若 \(B\)\(A\) 中一部分,则称 \(B\)\(A\)子公式

    定义 1.7

    1. 若公式 \(A\) 是单个的命题变项,则称 \(A\)\(0\) 层公式。
    2. 当出现下面的情况之一时,称 \(A\)\((n+1)\ (n\in\mathbb{N})\) 层公式。
      1. \(A = \neg B\) ,其中 \(B\)\(n\) 层公式。
      2. \(A = BRC\) ,其中 \(R \in \{\neg, \land, \lor, \rightarrow, \leftrightarrow\}\)\(B,C\) 分别为 \(b,c\) 层公式且 \(n = \max\{b,c\}\)

    定义 1.8\(p_1, \dots, p_n\ (n \in \mathbb{N}^*)\) 是出现在公式 \(A\) 中的全部命题变项,给它们各指定一个真值,称为对 \(A\) 的一个赋值/解释。若指定的一组值使 \(A\)\(1\) ,则称这组值为 \(A\)成真赋值,否则称为成假赋值

    定义 1.9 将命题公式 \(A\) 在所有赋值情况下取值情况列成的表称作 \(A\)真值表

    定义 1.10\(A\) 为任一命题公式。

    1. \(A\) 在其各种赋值下取值均为真,则称 \(A\)重言/永真
    2. \(A\) 在其各种赋值下取值均为假,则称 \(A\)矛盾/永假
    3. \(A\) 不是矛盾式,则称 \(A\)可满足式

    将被考虑而没有出现在公式 \(A\) 中的命题变项称为 \(A\)哑元,显然 \(A\) 的取值与其哑元无关。

    命题逻辑等值演算

    等值式

    定义 2.1\(A,B\) 是两个命题公式。若 \(A \leftrightarrow B\) 为重言式,则称 \(A\)\(B\)等值的,记作 \(A \Leftrightarrow B\)

    • 16 组常用的等值式模式

      1. 双重否定律

        \[A \Leftrightarrow \neg \neg A. \]

      2. 幂等律

        \[A \lor A \Leftrightarrow A \Leftrightarrow A \land A. \]

      3. 交换律

        \[A \land B \Leftrightarrow B \land A,\ A \lor B \Leftrightarrow B \lor A. \]

      4. 结合律

        \[(A \lor B) \lor C \Leftrightarrow A \lor (B \lor C),\ (A \land B) \land C \Leftrightarrow A \land (B \land C). \]

      5. 分配律

        \[A \lor (B \land C) \Leftrightarrow (A \lor B) \land (A \lor C),\ A \land (B \lor C) \Leftrightarrow (A \land B) \lor (A \land C) \]

      6. De Morgan律

        \[\neg (A\lor B) \Leftrightarrow \neg A \land \neg B,\ \neg (A \land B) \Leftrightarrow \neg A \lor \neg B \]

      7. 吸收律

        \[A \lor (A \land B) \Leftrightarrow A \Leftrightarrow A \land (A \lor B). \]

      8. 零律

        \[A \lor 1 \Leftrightarrow 1,\ A \land 0 \Leftrightarrow 0. \]

      9. 同一律

        \[A \lor 0 \Leftrightarrow A \Leftrightarrow A \land 1. \]

      10. 排中律

        \[A \lor \neg A \Leftrightarrow 1. \]

      11. 矛盾律

        \[A \land \neg A \Leftrightarrow 0. \]

      12. 蕴含等值式

        \[A \rightarrow B \Leftrightarrow \neg A \lor B. \]

      13. 等价等值式

        \[A \leftrightarrow B \Leftrightarrow (A \rightarrow B) \land (B \rightarrow A). \]

      14. 假言易位

        \[A \rightarrow B \Leftrightarrow \neg B \rightarrow \neg A. \]

      15. 等价否定等值式

        \[A \leftrightarrow B \Leftrightarrow \neg A \leftrightarrow \neg B. \]

      16. 归谬论

        \[(A \rightarrow B) \land (A \rightarrow \neg B) \Leftrightarrow \neg A. \]

    由已知的等值式推演出另外一些等值式的过程称作等值演算

    置换规则\(\varPhi(A)\) 是含公式 \(A\) 的命题公式, \(\varPhi(B)\) 是用公式 \(B\) 置换 \(\varPhi(A)\)\(A\) 的所有出现后得到的命题公式。若 \(A \Leftrightarrow B\) ,则 \(\varPhi(A) \Leftrightarrow \varPhi(B)\)

    析取范式与合取范式

    定义 2.2 命题变项及其否定统称作文字。仅由有限个文字构成的析取式称作简单析取式,仅由有限个文字构成的合取式称作简单合取式

    定理 2.1

    1. 一个简单合取式是矛盾式当且仅当它同时含某个命题变项及其否定式。
    2. 一个简单析取式是重言式当且仅当它同时含某个命题变项及其否定式。

    定义2.3 由有限个简单析取式的合取构成的命题公式称作合取范式,由有限个简单合取式的析取构成的命题公式称作析取范式,它们统称为范式

    定理 2.2

    1. 一个析取范式是矛盾式当且仅当组成它的每个简单合取式都是矛盾式。
    2. 一个合取范式是重言式当且仅当组成它的每个简单析取式都是重言式。

    定理 2.3(范式存在定理) 任一命题公式都存在与之等值的析取范式与合取范式。

    1. 消去 \(\rightarrow,\leftrightarrow\)
    2. 用双重否定律小区双重否定符,用 De Morgan 律内移否定符。
    3. 使用分配律。

    定义 2.4

    1. 在含有 \(n\) 个命题变项的简单合取式中,若每个命题变项及其否定式恰好出现一个且仅出现一次,而且命题变项或其否定式按照字典序排列,则称这样的简单合取式为极小项。每个极小项只有一个成真赋值 \(i\) ,因此可将该极小项记为 \(m_i\)
    2. 在含有 \(n\) 个命题变项的简单析取式中,若每个命题变项及其否定式恰好出现一个且仅出现一次,而且命题变项或其否定式按照字典序排列,则称这样的简单析取式为极大项。每个极大项只有一个成假赋值 \(i\) ,因此可将该极大项记为 \(M_i\)

    定理 2.4\(m_i\)\(M_i\) 分别是是命题变项 \(p_1,\dots,p_n\) 在赋值 \(i\) 下的极小项和极大项,则

    \[\neg m_i \Leftrightarrow M_i. \]

    定义 2.5 所有简单合取式都是极小项的析取范式称为主析取范式,所有简单析取式都是极大项的合取范式称为主合取范式

    定理 2.5 任何命题公式都存在等值的唯一主析取范式和唯一主合取范式。

    可满足性问题

    这一部分稍微涉及大二下学期课程《计算理论与算法分析》,因此稍作拓展介绍。

    定义

    \(w\mathrm{-cnf}\) 表示每个子句的文字数不大于 \(w\) 的合取范式。

    定义可满足/适定性(satisfiability)问题

    \[\mathrm{SAT} = \{\langle\varphi\rangle: \varphi\text{ 是可满足的布尔公式}\}, \]

    其中 \(\langle \cdot \rangle\) 表示编码。

    定义 \(l\) 元可满足性问题

    \[l\mathrm{-SAT} = \{\langle\varphi\rangle \in \mathrm{SAT}: \varphi \in l\mathrm{-cnf}\}. \]

    复杂度类型

    定理 \(2\mathrm{-SAT} \in \mathrm{P}\)。该定理在我们之后介绍了求解 \(2\mathrm{-SAT}\) 的算法后自然得证。

    Cook-Levin 定理 \(\mathrm{SAT} \in \mathrm{NPC}\)

    定理 \(\mathrm{SAT} =_\mathrm{p} 3\mathrm{-SAT} \in \mathrm{NPC}\)

    以上两个定理都可以通过多项式时间规约到最大团问题 \(\mathrm{CLIQUE}\) 来证明。

    可满足性问题是和图论问题高度相关的。下面的求解算法也会是采用图论的办法。

    \(2\mathrm{-SAT}\) 求解算法

    标准的 \(2\mathrm{-SAT}\) 是对每个子句都由恰好两个文字组成的合取范式寻找成真赋值。这里有一个不太好的地方:每个子句都是一个由恰好两个文字组成的简单析取式,我们想要这两个文字中“至少一个为真”,但这不是一个方便处理的关系。“蕴含”是比较好处理的关系,因为他的箭头很像图论里的有向边。

    因为 \(\{\neg, \to\}\) 是一组连结词完备集,所以我们可以用这两个符号来表示一切命题公式,自然也包括简单析取式。为此,我们改写一下问题,用简单蕴含式替代合取范式中的简单析取式。

    根据定义,\(2\mathrm{-SAT}\) 是这样一个问题:设有 \(n\) 个布尔变量 \(\vec{x} = (x_i)_{i = 0}^{n - 1}\),给定关于 \(\vec{x}\)\(m\)简单蕴含式 \(\vec{\varphi} = (\varphi_i)_{i = 0}^{m - 1} = (l_{i, 0} \to l_{i, 1})_{i = 0}^{m - 1}\),其中

    \[(\forall i \in \mathbb{N} \cap [0, n))\ (\forall j \in \{0, 1\})\ (\exists k \in \mathbb{N} \cap [0, n))\ (l_{i, j} \in \{\neg x_k, x_k\}), \]

    也就是说,每个文字 \(l_{i, j}\) 都是某个变量 \(x_k\) 或其否定 \(\neg x_k\)。我们需要找出 \(\vec{x}\) 的赋值,使得

    \[\varPhi = \bigwedge_{i = 0}^{n - 1} \varphi_i \]

    为真,即每个 \(\varphi_i\) 都为真。

    通常来说,求出一组赋值即可,或报告不存在成真赋值。

    具体来说,对每个变量的正负文字分别建立一个顶点,得到一个大小为 \(2n\) 的点集 \(V\)。记从文字到顶点的映射为 \(f\),显然 \(f\) 是双射。对于每个 \(\varphi_i = (l_{i, 0} \to l_{i, 1})\),建立从 \(f(l_{i, 0})\) 指向 \(f(l_{i, 1})\) 的有向边和从 \(f(\neg l_{i, 1})\) 指向 \(f(\neg l_{i, 0})\) 的有向边,即对每个蕴含式和它的逆否命题(以保证一个蕴含式被满足当且仅当其逆否也被满足)分别建边,得到边集 \(E\)

    甚么时候是无解的?当且仅当存在变量不得不同时为真和为假。这等价于这个变量的正文字和负文字等价,即其正文字能推出负文字其负文字能推出正文字。在这张图上,等价关系是怎样表示的?等价关系 \((x \leftrightarrow y) \Leftrightarrow ((x \to y) \land (y \to x))\) 在图上会表现为,从 \(f(x)\) 出发沿边走能够到达 \(f(y)\),从 \(f(y)\) 出发沿边走也能够到达 \(f(x)\),即 \(f(x)\)\(f(y)\) 强连通。所以我们只需执行一次有向图的 Tarjan 算法求出强连通分量,然后对每个变量检查其正负文字是否在同一个强连通分量中即可。

    那在知道了确实存在成真赋值的情况下,如何求出一组成真赋值呢?用强连通分量缩点后,我们可以得到一个有向无环图(directed acyclic graph, DAG)。对于一个变量 \(x\) 的正文字和负文字,他们之间如果连通,因为不是强连通,所以必然是单向连通;在蕴含式中,前件成立时要求后件必须成立,但后件成立时前件可以不成立;所以我们对于这对连通的正负文字,把在拓扑序中出现在后面的那一个赋值为真,这样无论出现在前面的那个是真是假都可以成立。特别地,因为 Tarjan 求出的强连通分量的编号是一个反拓扑序,所以我们可以直接比较正负文字所在强连通分量的编号。

    模板题 Luogu P4782 参考代码:

    inline void solve() {
    	size_t m, n;
    	std::cin >> n >> m;
    	Graph<void, true> graph(n << 1);
    	for (size_t i = 0; i < m; ++i) {
    		size_t u, v;
    		bool x, y;
    		std::cin >> u >> x >> v >> y;
    		--u, --v;
    		u = 2 * u + x;
    		v = 2 * v + y;
    		graph.insertEdge((u ^ 1), v);
    		graph.insertEdge((v ^ 1), u);
    	}
    	auto sccs = graph.tarjanSccs();
    	std::vector<size_t> scc_of(n << 1);
    	for (size_t i = 0; i < sccs.size(); ++i) {
    		for (auto v : sccs[i]) {
    			scc_of[v] = i;
    		}
    	}
    
    	std::vector<bool> vals(n);
    	for (size_t i = 0; i < n; ++i) {
    		if (scc_of[2 * i] == scc_of[2 * i + 1]) {
    			std::cout << "IMPOSSIBLE\n";
    			return;
    		}
    		vals[i] = (scc_of[2 * i + 1] < scc_of[2 * i]);
    	}
    	std::cout << "POSSIBLE\n";
    	for (size_t i = 0; i < n; ++i) {
    		std::cout << vals[i] << ' ';
    	}
    	std::cout << '\n';
    }
    

    时间复杂度主要来自 Tarjan 算法,即 \(\Theta(|V| + |E|) = \Theta(n + m)\)

    posted @ 2026-08-16 02:02  我就是蓬蒿人  阅读(16)  评论(0)    收藏  举报