2-SAT
1 概念
有这样一类问题,给出
2 求解过程
2-SAT,也就是给定若干变量以及若干二元限制,形如
考虑将这个问题转化成一个图论模型,将每个点拆成两个点,分别表示其取值为
接下来考虑判断有无解以及构造可行解。有无解很好判断,假如一个变量的两种取值都能互相走到,说明这个变量产生了矛盾,故无解。此时我们不难发现,这个变量的两种取值一定在同一个强联通分量内,所以跑一遍 Tarjan 缩点即可判断有无解。
构造可行解也很简单,对于一个变量的两种取值,我们看它们所属的强连通分量的拓扑序,显然如果满足拓扑序靠前的取值则另一种拓扑序靠后的取值也要满足,矛盾。所以我们只能满足拓扑序更靠后的取值。当然我们不用真的缩点然后跑拓扑排序,因为 Tarjan 缩点时给强联通分量赋上的实际上就是倒过来的拓扑序,所以改一下符号即可。
模板题:【模板】2-SAT,代码如下:
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 2e6 + 5;
const int Inf = 2e9;
int n, m;
int head[Maxn], edgenum;
struct node {
int nxt, to;
}edge[Maxn];
void add(int u, int v) {
edge[++edgenum] = {head[u], v};
head[u] = edgenum;
}
int V(int x, int a) {return a ? x : x + n;}
int dfn[Maxn], low[Maxn], ind, st[Maxn], top, ins[Maxn], bel[Maxn], scc;
void tarjan(int x) {
dfn[x] = low[x] = ++ind;
st[++top] = x;
ins[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(!dfn[to]) {
tarjan(to);
low[x] = min(low[x], low[to]);
}
else if(ins[to]) low[x] = min(low[x], dfn[to]);
}
if(dfn[x] == low[x]) {
scc++;
while(1) {
int v = st[top--];
ins[v] = 0;
bel[v] = scc;
if(v == x) break;
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i++) {
int x, a, y, b;
cin >> x >> a >> y >> b;
add(V(x, a ^ 1), V(y, b));
add(V(y, b ^ 1), V(x, a));//连边
}
for(int i = 1; i <= (n << 1); i++) {
if(!dfn[i]) tarjan(i);
}
for(int i = 1; i <= n; i++) {
if(bel[i] == bel[i + n]) {//两种取值位于同一强连通分量内
cout << "IMPOSSIBLE\n"; return 0;
}
}
cout << "POSSIBLE\n";
for(int i = 1; i <= n; i++) {
if(bel[i] < bel[i + n]) cout << "1 ";//Tarjan 上拓扑序靠前对应实际拓扑序靠后
else cout << "0 ";
}
return 0;
}
显然解决 2-SAT 问题的核心要点与其他图论问题一致,都是建出合适的图论模型然后套板子。
这里需要强调的一点是,2-SAT 的连边表示的是若
3 例题
例 1 [NEERC 2016] Binary Code
首先每个字符串最多只有一个 ?
,所以实际上一个字符串只有两种取值,不妨记作
考虑处理前缀关系的算法,显然可以想到 Trie 树。考虑将每个字符串的两种取值直接插入 Trie 树中,那么对于树上的两个终止节点,如果它们有祖先关系,那么它们都应该向对方的相反取值连边表示限制。但是这样做的时间和空间复杂度都难以支撑,考虑优化建图。
对于本题,最合适的优化方法就是前缀和优化建图。我们新建两个 Trie 树,一棵上每个点向父亲及当前节点上的相反取值连边,另一棵上每个点向儿子及当前节点上的相反取值连边。这样,我们对于每个取值,其对应终止节点往第一棵树上的父亲连边、往第二棵树上的儿子连边即可满足限制,且点边数均为
但是剩下的还有一点,就是同一个节点上我们可能也会有多个终止节点,这些节点中的每个点都要向其他点对应的相反取值连边,这个可以继续用前后缀优化建图。
最后就是固定的字符串了,我们可以假定其一位为 ?
,然后强制其必须选原先的那一位,即连边
例 2 [JSOI2019] 精准预测
建图方式本身并不难想到,将每个点对时间拆点,然后两种取值分别表示活和死。那么按照题目中所给的限制条件连边即可。为了满足死后不能复活的要求,我们还要对死点向其后面的死点连边,活点向其前面的活点连边。
现在的问题是我们的点数太多,有
然后考虑如何统计答案,我们应该从每个点在
现在的问题就转化为了一个有向图上可达性问题。进一步观察发现,我们连出来的图实际上是一个 DAG,所以可以直接拓扑排序 + bitset 解决这个问题。不过计算后发现这样做空间会炸,那么考虑经典的 bitset 分块,我们每一次只处理
最后还有一点就是如果一个点活可以推向这个点死,那么这个点无论如何都会死,它一定没有贡献且它自身的答案一定为
例 3 [NOI2017] 游戏
看到这个限制条件就可以想到 2-SAT,接下来我们发现,如果不管 x
地图,那么题目就是一个标准的 2-SAT,因为每张地图只有两种取值、限制条件是二元的。
如果加上 x
那么就不是很标准了,发现 x
地图跑什么车的话复杂度是 x
的具体取值,而是可以借助题目中给出的地图进行枚举。我们可以枚举每一个 x
作为 a
或 b
时的答案,这样显然 x
处的所有取值都会被考虑到,正确性得到保证。复杂度是
例 4 [POI2011] 同谋者 Conspiracy
题目中的限制显然可以转化为 2-SAT 问题。每个人只有两种取值:后勤或同谋。对每个人的后勤点向其不认识的人的同谋点连边,每个人的同谋点向其认识的人的后勤点连边即可。边数是标准的
然后难点在于求出这个 2-SAT 解的个数。这个问题在一般条件下是不可解决的,但是这个题有一个特殊性质:对于一个方案,每一组中最多挑出一个人换到另一个组才可能得到另一个合法方案。证明显然,如果同时挑了两个人的话这两个人一定不可能满足另一组的限制。
那么我们先跑一边 2-SAT 得出一个合法方案,然后开始调整得到所有方案。具体的,我们只有两种可能:将一个人换组或将两个人交换。对于前者,需要要求这个人换组后没有冲突且该组还有剩余的人;对于后者,要么两个人换组后没有冲突,要么换组后冲突的人只有一个且就是对方。直接累加合法答案即可,复杂度
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2024-03-03 2024.3 做题记录