2–SAT 学习笔记
现在 2–SAT 已经是蓝题了吗。
由于我的模板都不知道是多少年前写的了,所以就有了这边文章。
2–SAT 问题
模板题链接:P4782 【模板】2–SAT
题意:
有 $n$ 个布尔变量 $x_1,x_2,\cdots,x_n$,另有 $m$ 个需要满足的条件,每个条件的形式都是 「$x_i$ 为
true
/false
或 $x_j$ 为true
/false
」。比如 「$x_1$ 为真或 $x_3$ 为假」、「$x_7$ 为假或 $x_2$ 为假」。2–SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入格式:
第一行两个整数 $n$ 和 $m$,意义如题面所述。
接下来 $m$ 行每行 $4$ 个整数 $i,a,j,b$,表示 「$x_i$ 为 $a$ 或 $x_j$ 为 $b$」$(a, b\in \{0,1\})$
输出格式:
如无解,输出
IMPOSSIBLE
;否则输出POSSIBLE
。下一行 $n$ 个整数 $x_1,x_2,\cdots, x_n~(x_i\in\{0,1\})$,表示构造出的解。
数据范围:
$1\leq n, m\leq 10^6$
SAT 问题(satisfiability problem)指解决给定限制下,若干布尔变量的可满足性问题。
例如我想要知道
对于布尔变量 $a,b,c$ 的一种可行方案
这里出现了 $3$ 个变量,即这是一个 3–SAT 问题;而本题只有两个变量和 $m$ 条限制,即 2–SAT 问题。
然而除了 2–SAT 问题以外的 SAT 问题均为 NPC 问题,因此关于 SAT 问题这部分的介绍就到此为止了。
现在考虑怎么做本题。我们把每个点看作 $x$ 和 $\lnot x$ ,不妨编号为 $x$ 和 $x + n$ 。
那么对于每条限制,我们可以转化为图上的一条边
对应的代码就是:
while(m--)
{
static int i, j; static bool a, b;
cin >> i >> a >> j >> b;
vec[i + n * a].pb(j + n * (b ^ 1));
vec[j + n * b].pb(i + n * (a ^ 1));
}
注意到存在某个点 $x$ 满足 $x$ 和 $\lnot x$ 在同一个强连通分量中,一定无解。
对于有解的情况,我们取命题「 $x$ 的拓扑序大于 $\lnot x$ 的拓扑序」的值即可,这里拓扑序指缩点后的拓扑序。
不过由于 Tarjan 缩点以后每个点的编号正好是逆拓扑序,我们用小于判断即可。
时间复杂度 $\mathcal{O}(n + m)$
代码:(注意空间要开两倍)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define pb push_back
#define N ((int)(2e6 + 15))
bool ins[N];
vector<int> vec[N];
int stktop, scnt, stk[N], low[N], dfn[N], scc[N], top[N];
void Tarjan(int u)
{
static int tim = 0; dfn[u] = low[u] = ++tim;
stk[++stktop] = u; ins[u] = true;
for(int v : vec[u])
{
if(!dfn[v]) { Tarjan(v); down(low[u], low[v]); }
else if(ins[v]) down(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
top[++scnt] = u;
for(int p = 0; p != u; )
{
p = stk[stktop--];
ins[p] = false; scc[p] = scnt;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n, m; cin >> n >> m;
while(m--)
{
static int i, j; static bool a, b;
cin >> i >> a >> j >> b;
vec[i + n * a].pb(j + n * (!b));
vec[j + n * b].pb(i + n * (!a));
}
rep(i, 1, n * 2) if(!dfn[i]) Tarjan(i);
rep(i, 1, n) if(scc[i] == scc[i + n]) return cout << "IMPOSSIBLE\n", 0;
cout << "POSSIBLE\n";
rep(i, 1, n) cout << (scc[i] < scc[i + n]) << " \n"[i == n];
return 0;
}