「学习笔记」2-SAT

一.什么是 \(\text{2-SAT}\)

\(SAT\) 是适定性 \(Satisfiability\) 问题的简称。一般形式为 \(k -\)适定性问题,简称 \(\text{k-SAT}\)。而当 \(k>2\) 时,该问题为 \(NP\) 完全的。所以我们只研究 \(k=2\) 的情况。

形象地来说,给定 \(n\) 个布尔变量 \(a_i\),同时给出若干个约束条件:

\((\text{not})a_i\) \(op\) \((\text{not})a_j = \text{true/false}\)

而求解 \(\text{2-SAT}\) 就是求出一组 \(a_i\) 的解。

二.将 \(\text{2-SAT}\) 问题转换为图论问题

首先定义一条有向边的定义 \(x\to y\):如果满足 \(x\) 就必须满足 \(y\)

发现一个点有两个状态真或假,可以想到将点 \(u\) 拆为点 \(u_0,u_1\),分别表示假或真。

\(1.\) \(a\land b = \text{true}\) \(\to (a1, b1),(b1, a1)\)

\(2.\) \(a\land b = \text{false}\) \(\to (a1, b0),(b1, a0)\)

\(3.\) \(a\lor b = \text{true}\) \(\to (a0, b1),(b0, a1)\)

\(4.\) \(a\lor b = \text{false}\) \(\to (a0, b0),(b0, a0)\)

这样我们就将 \(\text{2-SAT}\) 问题转换为图论问题了,可以运用图论的方式解决。

三.解决 \(\text{2-SAT}\) 问题

  • \(1.\) 判断有无解

    由上文定义的状态可以得知,\(u_0\)\(u_1\) 不能同时被满足。
    那么存在点 \(x_0\)\(x_1\) 在同一个强连通分量中时,那么无解。
    反之则有解。

  • \(2.\) 求方案

    由我们的建边可得:对于每个点 \(u\),选择其拓扑序较大的那种状态更优。
    为什么呢?
    如下图所示:
    image

    对于 \(x1\) 的两种状态,如果选择 \(\text{false}\),那么推导出 \(x2\)\(\text{true}\),又推导出 \(x1\)\(\text{true}\),又推导出 \(x2\)\(\text{false}\),发现这种解法矛盾了。
    但如果对于 \(x1\) 选择 \(\text{true}\),那么 \(x2\)\(\text{false}\),则满足要求。
    由此我们得出:对于每个点 \(u\),选择其拓扑序较大的那种状态更优。
    这里不需要再做一遍拓扑排序求拓扑序,在 \(\text{Tarjan}\) 求强连通分量中就求出了每个点的逆拓扑序,每个点的强连通分量编号也就是逆拓扑序。

四.例题讲解

P4782 【模板】2-SAT 问题

\(\text{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] 满汉全席

设汉式的点编号为 \(1到n\),满式的点编号为 \(n+1到2n\)

那么就按照模板建边解决 \(\text{2-SAT}\) 问题即可。

UVA11294 Wedding

题意:有 \(n\) 对夫妻分别坐在新郎与新娘两侧,有两个要求:

\(1.\) 一对夫妻不能坐在同一侧;

\(2.\) 给出 \(m\) 个特殊关系,有特殊关系的两个人不能同时坐在新郎一侧,也就是说可以分别坐两侧,或者同时坐在新娘一侧。

我们设女方的 \(0\) 状态为 \(1到n\),男方的 \(0\) 状态为 \(n+1到2n\),女方的 \(1\) 状态为 \(2n+1到3n\),男方的 \(1\) 状态为 \(3n+1到4n\)

同时设 \(0\) 状态在左边,也就是新娘一侧。\(1\) 状态在右边,也就是新郎一侧。

这样就可以建边了解决 \(\text{2-SAT}\) 问题了。

建边代码如下:

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

也是模板。

拆点后建边跑 \(\text{Tarjan}\),然后用 \(\text{dfs}\) 判断点的可行情况即可。

posted @ 2022-02-08 21:27  cyhyyds  阅读(61)  评论(0编辑  收藏  举报