Loading

「学习笔记」2-SAT问题

SAT 是适定性 (Satisfiability) 问题的简称。一般形式为 k - 适定性问题,简称 k-SAT。而当 \(k>2\) 时该问题为 NP 完全的。所以我们只研究 \(k=2\) 的情况。
2-SAT,简单的说就是给出 \(n\) 个集合,每个集合有两个元素,已知若干个 \(<a,b>\),表示 \(a\)\(b\) 矛盾(其中 \(a\)\(b\) 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 \(n\) 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。

建图

我们将 \(n\) 个集合拆成两个点,分别代表着 truefalse.
a || b == true 时,我们可以得知:
a == false 时,b = true
b == false 时,a = true;
这两个关系存在因果关系;
a == true 时,我们不能得知 b = true 还是 b = false,这两者之间不存在因果关系,b == true 同理。
因此,我们将 \(a_0\)\(b_1\) 连边,将 \(b_0\)\(a_1\) 连边。

含义:
a == false 时,b = true
b == false 时,a = true

a && b == false 时,我们可以得知:
a == true 时,b = false;
b == true 时,a = false;
我们将 \(a_1\)\(b_0\) 连边,将 \(b_1\)\(a_0\) 连边。

含义:
a == true 时,b = false;
b == true 时,a = false

a && b == true 时,我们发现,a == trueb == true,除了这种情况不会再有其他情况了,即 \(a\) 的值一定为 true\(b\) 的值一定为 true
这种情况下,我们将 \(a_0\)\(a_1\) 连边,\(b_0\)\(b_1\) 连边。

含义:
a == false 时,a = true;(即 \(a\) 一定不为 false
b == false 时,b = true。(即 \(b\) 一定不为 true)

判断是否有解

如果 \(a_0\) 可以到达 \(a_1\),说明 \(a\) 一定为 true
如果 \(a_1\) 可以到达 \(a_0\),说明 \(a\) 一定为 false
判断是否有解即在一种情况中 \(a\) 都有唯一确定的值,要么为 true,要么为 false,倘若在同一种情况中,\(a_0\) 可以到达 \(a_1\)\(a_1\) 可以到达 \(a_0\),则无法确定 \(a\) 的值,此情况下无解,即 \(a_0\)\(a_1\) 在同一个强连通分量里。
用 tarjan 算法来找强连通分量即可。

题目

P4782 【模板】2-SAT 问题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1e6 + 5;

int n, m, tim, scc;
vector<int> son[N << 1], sta;
int dfn[N << 1], low[N << 1], lt[N << 1];

void tarjan(int u) {
	dfn[u] = low[u] = ++ tim;
	sta.push_back(u);
	for (int v : son[u]) {
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (!lt[v]) {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		lt[u] = ++ scc;
		while (sta.back() != u) {
			lt[sta.back()] = scc;
			sta.pop_back();
		}
		sta.pop_back();
	}
}

int main() {
	scanf("%d%d", &n, &m);
	while (m --) {
		int xi, i, xj, j;
		scanf("%d%d%d%d", &xi, &i, &xj, &j);
		son[xi + n * (i & 1)].push_back(xj + (j ^ 1) * n);
		son[xj + n * (j & 1)].push_back(xi + (i ^ 1) * n);
	}
	for (int i = 1; i <= (n << 1); ++ i) {
		if (!dfn[i]) {
			tarjan(i);
		}
	}
	for (int i = 1; i <= n; ++ i) {
		if (lt[i] == lt[i + n]) {
			puts("IMPOSSIBLE");
			return 0;
		}
	}
	puts("POSSIBLE");
	for (int i = 1; i <= n; ++ i) {
		printf("%d%c", (lt[i] < lt[i + n]), " \n"[i == n]);
	}
	return 0;
}

CF1475F
\(A\)\(B\) 两个矩阵异或,得到一个新矩阵 \(C\),若 \(C\) 可以通过异或行或列的操作来变成全 \(0\) 矩阵,那么说明 \(A\) 可以通过异或得到 \(B\)
我们发现,每一行或每一列要么不异或,要么异或一次,由此可以想到 2-SAT。
对于 \(C\) 中的元素,若 \(C(i, j)\)\(1\),则要么行异或,要么列异或;若 \(C(i, j)\)\(0\),则要么行和列都异或,要么行和列都不异或,由此建边判断是否有解。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1005;

int T, n, cnt, tim, scc;
int a[N][N], b[N][N], c[N][N], x[N][2], y[N][2];
int dfn[N << 2], low[N << 2], lt[N << 2];
vector<int> son[N << 2], Stack;

void init() {
	cnt = tim = scc = 0;
	for (int i = 1; i <= n; ++ i) {
		x[i][1] = ++ cnt;
		son[cnt].clear();
		dfn[cnt] = lt[cnt] = 0;
	}
	for (int i = 1; i <= n; ++ i) {
		x[i][0] = ++ cnt;
		son[cnt].clear();
		dfn[cnt] = lt[cnt] = 0;
	}
	for (int i = 1; i <= n; ++ i) {
		y[i][1] = ++ cnt;
		son[cnt].clear();
		dfn[cnt] = lt[cnt] = 0;
	}
	for (int i = 1; i <= n; ++ i) {
		y[i][0] = ++ cnt;
		son[cnt].clear();
		dfn[cnt] = lt[cnt] = 0;
	}
}

void tarjan(int u) {
	dfn[u] = low[u] = ++ tim;
	Stack.push_back(u);
	for (int v : son[u]) {
		if (! dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (! lt[v]) {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		lt[u] = ++ scc;
		while (Stack.back() != u) {
			lt[Stack.back()] = scc;
			Stack.pop_back();
		}
		Stack.pop_back();
	}
}

int main() {
	scanf("%d", &T);
	while (T --) {
		scanf("%d", &n);
		init();
		for (int i = 1; i <= n; ++ i) {
			for (int j = 1; j <= n; ++ j) {
				scanf("%1d", &a[i][j]);
			}
		}
		for (int i = 1; i <= n; ++ i) {
			for (int j = 1; j <= n; ++ j) {
				scanf("%1d", &b[i][j]);
				c[i][j] = a[i][j] ^ b[i][j];
				if (c[i][j]) {
					son[x[i][1]].push_back(y[j][0]);
					son[y[j][0]].push_back(x[i][1]);
					son[x[i][0]].push_back(y[j][1]);
					son[y[j][1]].push_back(x[i][0]);
				}
				else {
					son[x[i][1]].push_back(y[j][1]);
					son[y[j][1]].push_back(x[i][1]);
					son[x[i][0]].push_back(y[j][0]);
					son[y[j][0]].push_back(x[i][0]);
				}
			}
		}
		for (int i = 1; i <= cnt; ++ i) {
			if (! dfn[i]) {
				tarjan(i);
			}
		}
		int fg = 0;
		for (int i = 1; i <= n; ++ i) {
			if (lt[x[i][1]] == lt[x[i][0]]) {
				puts("NO");
				fg = 1;
				break;
			}
			if (lt[y[i][1]] == lt[y[i][0]]) {
				puts("NO");
				fg = 1;
				break;
			}
		}
		if (! fg)	puts("YES");
	}
	return 0;
}
posted @ 2023-05-04 09:11  yi_fan0305  阅读(134)  评论(2编辑  收藏  举报