「学习笔记」2-SAT
一.什么是 #
是适定性 问题的简称。一般形式为 适定性问题,简称 。而当 时,该问题为 完全的。所以我们只研究 的情况。
形象地来说,给定 个布尔变量 ,同时给出若干个约束条件:
。
而求解 就是求出一组 的解。
二.将 问题转换为图论问题#
首先定义一条有向边的定义 :如果满足 就必须满足 。
发现一个点有两个状态真或假,可以想到将点 拆为点 ,分别表示假或真。
。
。
。
。
这样我们就将 问题转换为图论问题了,可以运用图论的方式解决。
三.解决 问题#
-
判断有无解#
由上文定义的状态可以得知, 和 不能同时被满足。
那么存在点 和 在同一个强连通分量中时,那么无解。
反之则有解。 -
求方案#
由我们的建边可得:对于每个点 ,选择其拓扑序较大的那种状态更优。
为什么呢?
如下图所示:
对于 的两种状态,如果选择 ,那么推导出 为 ,又推导出 为 ,又推导出 为 ,发现这种解法矛盾了。
但如果对于 选择 ,那么 为 ,则满足要求。
由此我们得出:对于每个点 ,选择其拓扑序较大的那种状态更优。
这里不需要再做一遍拓扑排序求拓扑序,在 求强连通分量中就求出了每个点的逆拓扑序,每个点的强连通分量编号也就是逆拓扑序。
四.例题讲解#
P4782 【模板】2-SAT 问题#
模板题。
按照上文建边,求解即可。
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <stack>
using namespace std;
const int N = 2000005;
struct EDGE {
int nxt;
int to;
}e[N];
int cnt = 1, sjc = 0, dfn[N], low[N], col[N], co = 0, head[N], edge_num = 0;
int sta[N], top = 0;
stack<int> s;
bool insta[N];
inline void add_edge (int x, int y) {
e[++edge_num].nxt = head[x];
e[edge_num].to = y;
head[x] = edge_num;
}
inline void tarjan (int u) {
sta[++top] = u;
low[u] = dfn[u] = ++sjc;
insta[u] = true;
for (int i = head[u] ;~i ; i = e[i].nxt) {
int v = e[i].to;
if (!dfn[v]) {
tarjan (v);
low[u] = min (low[u], low[v]);
}
else if (insta[v]) {
low[u] = min (low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
cnt ++;
int tp;
do {
tp = sta[top--];
col[tp] = cnt;
insta[tp] = false;
}while (tp != u);
}
}//Tarjan求强连通分量。
int main() {
memset (head, -1, sizeof (head));
int n, m;
scanf ("%d%d", &n, &m);
while (m --) {
int i, a, j, b;
scanf ("%d%d%d%d", &i, &a, &j, &b);
if (a == 0 && b == 0) {//a=0或b=0
add_edge (i + n, j);//i真j假
add_edge (j + n, i);//j真i假
}
if (a == 0 && b == 1) {//a=0或b=1
add_edge (i + n, j + n);//i真j假
add_edge (j, i);//j假i真
}
if (a == 1 && b == 0) {//a=1或b=0
add_edge (i, j);//i假j假
add_edge (j + n, i + n);//j真i真
}
if (a == 1 && b == 1) {//a=1或b=1
add_edge (i, j + n);//i假j真
add_edge (j, i + n);//j假i真
}
}
for (int i = 1; i <= (n << 1); i ++) {
if (!dfn[i]) {
tarjan (i);
}
}
for (int i = 1; i <= n; i ++) {
if (col[i] == col[i + n]) {//无解状态。
puts ("IMPOSSIBLE");
return 0;
}
}
puts ("POSSIBLE");
for (int i = 1; i <= n; i ++) {
printf ("%d ", col[i] > col[i + n] ? 1 : 0);
//选择拓扑序大的。
}
return 0;
}
P4171 [JSOI2010] 满汉全席#
设汉式的点编号为 ,满式的点编号为 。
那么就按照模板建边解决 问题即可。
UVA11294 Wedding#
题意:有 对夫妻分别坐在新郎与新娘两侧,有两个要求:
一对夫妻不能坐在同一侧;
给出 个特殊关系,有特殊关系的两个人不能同时坐在新郎一侧,也就是说可以分别坐两侧,或者同时坐在新娘一侧。
我们设女方的 状态为 ,男方的 状态为 ,女方的 状态为 ,男方的 状态为 。
同时设 状态在左边,也就是新娘一侧。 状态在右边,也就是新郎一侧。
这样就可以建边了解决 问题了。
建边代码如下:
Add(1 + 2 * n, 1);//强制新娘在左边
Add(1 + n, 1 + n + 2 * n);//强制新郎在右边
for (int i = 2; i <= n; ++i) {
int x = i, y = i + n; //x女方0状态,y男方0状态
Add(x, y + 2 * n);
Add(y + 2 * n, x);
Add(x + 2 * n, y);
Add(y, x + 2 * n);
//双方0状态与1状态互相连边
}
while (m --) {
int x, y;
char a, b;
scanf("%d%c%d%c", &x, &a, &y, &b);
x ++, y ++;//调整编号为1-n
if (a == 'h') {
x += n;
}
if (b == 'h') {
y += n;
}
//男方要加一个n
Add(x + 2 * n, y);
Add(y + 2 * n, x);
//双方不在同一侧
}
P3007 [USACO11JAN] The Continental Cowngress G#
也是模板。
拆点后建边跑 ,然后用 判断点的可行情况即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效