题解 P4782 【【模板】2-SAT 问题】
zimindaada · · 题解
博客阅读更佳
SAT问题的定义
SAT是Satisfiability(可满足性)的缩写,而k-SAT问题就是说你有bool变量(a or b or !c) 或 a and !b and !c 的条件)为true,或定义某一个未知数为true/false的一种解法。
很容易想到bool未知数的值,判断是否合法。而对于
2-SAT问题的解法
定义对于任何一个单字母bool变量true。
我们通过建一些特殊的图来实现:我们令编号从true的情况,false的情况。连边方面,我们令从
然后我们对于这样一个特殊的图跑一边强连通分量,如果存在
于是我们得到了一张拓扑图。有一个性质,就是在有解的情况下,一个未知数的两个取值是有前后推导关系的,也就是一个取值直接或间接的指向了另一个取值时,我们取后者是一定有解的。实际实现上,输出的时候,输出对于每一个点拆除的true和false中,拓扑序较大的那一个,则一定有一个解。然而由于我们跑了Tarjan,那么我们可以附带着求一个反向的拓扑序,那么这就拓扑序也求得了。
从刚才这一角度来说,对于刚才建图中的第三种情况,我们连
很明显复杂度瓶颈在Tarjan跑强连通分量上,其复杂度为
例题
例题1 P4782 【模板】2-SAT 问题
一道模板题,甚至连赋值操作都没有。可以看到这里输入的时候用了一些小技巧来压了一下输入的行,其他就没什么难以理解的了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
namespace ztd{
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int inf = 2147483647;
template<typename T> inline T read(T& t) {//fast read
t=0;short f=1;char ch=getchar();double d = 0.1;
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
t*=f;
return t;
}
}
using namespace ztd;
const int maxn = 2e6+7, maxm = 2e6+7;
int n, m;
int last[maxn], ecnt;
struct edge{int y, gg;}e[maxm];
inline void addedge(int x, int y){
e[++ecnt] = {y, last[x]};
last[x] = ecnt;
}
int dfn[maxn], low[maxn], pcnt, st[maxn], top, gang[maxn], gtot;
bool instack[maxn];
void Tarjan(int x){
dfn[x] = low[x] = ++pcnt;
st[++top] = x; instack[x] = 1;
for(int i = last[x], y = e[i].y; i; i = e[i].gg, y = e[i].y){
if(!dfn[y]){
Tarjan(y);
low[x] = min(low[x], low[y]);
}else if(instack[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]){
int now; ++gtot;
do{
now = st[top--]; instack[now] = 0;
gang[now] = (gtot);
}while(now ^ x);
}
}
int main(){
read(n); read(m);
int xx, yy, hmx, hmy;
for(int i = 1; i <= m; ++i){
read(xx); read(hmx); read(yy); read(hmy);
addedge(xx+(1-hmx)*n, yy+hmy*n);
addedge(yy+(1-hmy)*n, xx+hmx*n);
}
for(int i = 1; i <= n*2; ++i) if(!dfn[i]) Tarjan(i);
for(int i = 1; i <= n; ++i){
if(gang[i] == gang[i+n]){
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(int i = 1; i <= n; ++i){
cout << ((int)(gang[i] > gang[i+n])) << " ";
}
cout << '\n';
return 0;
}
还有几道比较好的练手题,如
P4171 【JSOI2010】满汉全席(跟模板区别不大)
P3825 【NOI2017】游戏(比较好的实际应用题)
关于这些题的题解,可以在我的博客里看。