「算法笔记」2-SAT 问题
一、定义
k-SAT(Satisfiability)问题的形式如下:
有 \(n\) 个 01 变量 \(x_1,x_2,\cdots,x_n\),另有 \(m\) 个变量取值需要满足的限制。
每个限制是一个 \(k\) 元组 \((x_{p_1},x_{p_2},\cdots,x_{p_k})\),满足 \(x_{p_1}\oplus x_{p_2}\oplus\cdots\oplus x_{p_k}=a\)。其中 \(a\) 为 \(0\) 或 \(1\),\(\oplus\) 是某种二元 bool 运算(如 或运算 \(\vee\)、与运算 \(\wedge\))。
要求构造一种满足所有限制的变量的赋值方案。
当 \(k>2\) 时该问题为 NP 完全的,只能暴力求解。因此一般讨论的是 \(k=2\) 的情况,即 2-SAT 问题。
二、基本思想
以 Luogu P4782 【模板】2-SAT 问题 为例,建立图论模型。
\(m\) 个限制,每个限制的形式都是 「\(x_i\) 为 真/假 或 \(x_j\) 为 真/假」。
对于变量 \(x_i\),建立两个点 \(i\) 与 \(i+n\),分别表示 \(x_i\) 为真、\(\neg x_i\) 为真。
若 \(x\) 为真,则 \(\neg x\) 为假;若 \(\neg x\) 为假,则 \(x\) 为真。反之亦然。显然 \(x\) 和 \(\neg x\) 是互斥的。即,点 \(i\) 与 \(i+n\) 分别表示 \(x_i\) 为真或假。
对变量关系建有向图。有向边 \(u\to v\) 表示,若 \(u\) 为真,则 \(v\) 一定为真。
具体地,对于每个限制 \((a\vee b)\)(变量 \(a,b\) 至少满足一个),可将其转化为 \(\neg a\rightarrow b\wedge\neg b\rightarrow a\)(\(a\) 为假则 \(b\) 一定为真;\(b\) 为假则 \(a\) 一定为真)。即节点 \(\neg a\) 向节点 \(b\) 连边,从节点 \(\neg b\) 向节点 \(a\) 连边。
考虑节点 \(i\) 与 \(i+n\) 在图中的关系。若它们 互相可达,即在 同一个强连通分量 中,则说明在赋值限制下,它们代表的一对互斥取值会同时被取到。则不存在一组合法的赋值方案。
否则,说明有解,考虑如何构造一组合法解。
首先,对建出的图进行缩点得到一个 DAG。考虑节点 \(i\) 与 \(i+n\) 所在强连通分量的 拓扑关系。若两分量不连通,则 \(x_i\) 取任意值(真或假)。否则只能取属于拓扑序较大的分量的值。因为若取拓扑序较小的值,可以根据逻辑关系推出取另一个值也是同时发生的。
三、具体实现
以 Luogu P4782 【模板】2-SAT 问题 为例。
对于每个限制 \((a\vee b)\)(变量 \(a,b\) 至少满足一个),节点 \(\neg a\) 向节点 \(b\) 连边,从节点 \(\neg b\) 向节点 \(a\) 连边。
用 Tarjan 算法对建出的图缩点。
对于 \(i\in [1,n]\),若 \(i\) 与 \(i+n\) 在同一个强连通分量中,则不存在一组合法的赋值方案。
否则,根据 Tarjan 求得的强连通分量的标号为拓扑逆序(Tarjan 算法求强连通分量时使用了栈),即反向的拓扑序 ,可以得到 \(x_i\) 的值(取 \(i\) 与 \(i+n\) 所在强连通分量拓扑序较大的点的值)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,m,x,a,y,b,cnt,hd[N],to[N<<1],nxt[N<<1],tot,c[N],top,s[N],num,dfn[N],low[N];
void add(int x,int y){
to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
}
void tarjan(int x){
dfn[x]=low[x]=++num,s[++top]=x;
for(int i=hd[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
else if(!c[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
c[x]=++tot;
while(s[top]!=x) c[s[top--]]=tot;
--top;
}
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
scanf("%lld%lld%lld%lld",&x,&a,&y,&b);
if(a&&b) add(x+n,y),add(y+n,x);
if(!a&&b) add(x,y),add(y+n,x+n);
if(a&&!b) add(x+n,y+n),add(y,x);
if(!a&&!b) add(x,y+n),add(y,x+n);
}
for(int i=1;i<=2*n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
if(c[i]==c[i+n]) puts("IMPOSSIBLE"),exit(0);
puts("POSSIBLE");
for(int i=1;i<=n;i++)
printf("%d%c",c[i]<c[i+n],i==n?'\n':' '); //Tarjan 求得的强连通分量的标号为拓扑逆序,即反向的拓扑序
return 0;
}
「算法笔记」2-SAT 问题的更多相关文章
- 「算法笔记」快速数论变换(NTT)
一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...
- 「算法笔记」树形 DP
一.树形 DP 基础 又是一篇鸽了好久的文章--以下面这道题为例,介绍一下树形 DP 的一般过程. POJ 2342 Anniversary party 题目大意:有一家公司要举行一个聚会,一共有 \ ...
- 「算法笔记」Polya 定理
一.前置概念 接下来的这些定义摘自 置换群 - OI Wiki. 1. 群 若集合 \(s\neq \varnothing\) 和 \(S\) 上的运算 \(\cdot\) 构成的代数结构 \((S, ...
- 「算法笔记」状压 DP
一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...
- 「算法笔记」旋转 Treap
一.引入 随机数据中,BST 一次操作的期望复杂度为 \(\mathcal{O}(\log n)\). 然而,BST 很容易退化,例如在 BST 中一次插入一个有序序列,将会得到一条链,平均每次操作的 ...
- 「算法笔记」FHQ-Treap
右转→https://www.cnblogs.com/mytqwqq/p/15057231.html 下面放个板子 (禁止莱莱白嫖板子) P3369 [模板]普通平衡树 #include<bit ...
- 「算法笔记」Min_25 筛
戳 这里(加了密码).虽然写的可能还算清楚,但还是不公开了吧 QwQ. 真的想看的 私信可能会考虑给密码 qwq.就放个板子: //LOJ 6053 简单的函数 f(p^c)=p xor c #inc ...
- 「算法笔记」快速傅里叶变换(FFT)
一.引入 首先,定义多项式的形式为 \(f(x)=\sum_{i=0}^n a_ix^i\),其中 \(a_i\) 为系数,\(n\) 为次数,这种表示方法称为"系数表示法",一个 ...
- 「算法笔记」BSGS 与 exBSGS
一.离散对数 给定 \(a,b,m\),存在一个 \(x\),使得 \(\displaystyle a^x\equiv b\pmod m\) 则称 \(x\) 为 \(b\) 在模 \(m\) 意义下 ...
随机推荐
- 云原生时代,为什么基础设施即代码(IaC)是开发者体验的核心?
作者 | 林俊(万念) 来源 |尔达 Erda 公众号 从一个小故事开始 你是一个高级开发工程师. 某天,你自信地写好了自动煮咖啡功能的代码,并在本地调试通过.代码合并入主干分支后,你准备把服务发布到 ...
- Hadoop fs.copyToLocalFile错误
fs.copyToLocalFile(new Path("/study1/1.txt"), new Path("C:/Users/Administrator/Deskto ...
- Docker学习(一)——安装docker
Suse12上安装docker 对于suse13.2之后的版本,因为docker已经被添加到了suse仓库中,直接使用sudo zypper install docker即可. suse12不 ...
- C++ 写出这个数
题目如下: 读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 1. 输出格式: 在一行内 ...
- 【编程思想】【设计模式】【其他模式】graph_search
Python版 https://github.com/faif/python-patterns/blob/master/other/graph_search.py #!/usr/bin/env pyt ...
- mysql数据库备份脚本一例
例子,mysql数据库备份脚本.vim mysql.sh #!/bin/bash DAY=`date +%Y-%m-%d` //日期以年月日显示并赋予DAY变量 SIZE=`du -sh /var/l ...
- 第7章 使用性能利器——Redis
在现今互联网应用中,NoSQL已经广为应用,在互联网中起到加速系统的作用.有两种NoSQL使用最为广泛,那就是Redis和MongoDB.本章将介绍Redis和Spring Boot的结合.Redis ...
- 快速上手ANTLR
回顾前文: ANTLR 简单介绍 ANTLR 相关术语 ANTLR 环境准备 下面通过两个实例来快速上手ANTLR. 使用Listener转换数组 完整源码见:https://github.com/b ...
- Moment.js使用笔记
零.前情提要 上个月开发了数据平台,用的框架是vue + Ant Design of Vue,其中用了组件[range-picker]日期选择框,涉及到时间方法就去看了momentJS,以此记录~ 如 ...
- python f-strings !表达式
近期看一些框架的文档时发现, python的f-strings有f"{xxx!r}"的写法, 就官网看了一波文档, 特此记录一下, 顺便完善一下f-strings的使用 f-str ...