2-SAT 1
2-SAT 模板
有 \(n\) 个布尔变量 \(x_1\)\(\sim\)\(x_n\),
另有 \(m\) 个需要满足的条件,每个条件的形式都是 「\(x_i\) 为 true
/ false
或 \(x_j\) 为 true
/ false
」。
比如 「\(x_1\) 为真或 \(x_3\) 为假」、「\(x_7\) 为假或 \(x_2\) 为假」。
\(2-SAT\) 问题的目标是给每个变量赋值使得所有条件得到满足。
一种最直接的方式就是暴力搜,时间复杂度\(O(2^n)\)
已经被证明的就是\(k-SAT\)对于\(k>2\)的部分是\(NPC\)问题
但是\(2-SAT\)还是能做的
判解
图论的一个非常重要的内容就是建图
我们把每个变量看成一个点,方程看作一条边
直接连边?显然会麻烦好多
这里的做法是拆点,
就是\(x_i\)取\(1\)的情况和\(x_i\)取\(0\)的情况在图上对应两个点
而对应的,一个条件也会被拆成:
条件本身和它的逆否
从取\(0\)变量对应的那个点向取\(1\)的点连有向边
它的实际含义就是"如果取\(0\)的变量取\(0\),取\(1\)的变量必须取\(1\)"
这样的好处不仅是容易理解,我们还会发现:
如果两个对立的点在同一个强连通分量,显然无解
实际含义就是"如果这个点取一个值并且还要满足所有条件,那么这个点必须取这个值的反面"
于是我们只需要一遍\(Tarjan\),就可以解决这个问题
构造可行解
听论文说,构造一组可行解,
就是从缩完点以后的\(DAG\)上自底向上删点
用下一个才学的\(Tarjan\)性质,
就是\(SCC\)的编号越小,表示在\(DAG\)上越靠后
于是选\(SCC\)编号小的即可
模板
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int o=2222222*2;
int n,m;
struct Graph{
struct edge{
int t;
int n;
}p[o];
int h[o],cnt;
int d[o],l[o],s[o],v[o],c[o],top,sum,num;
void add(int s,int t){
cnt++;
p[cnt].t=t;
p[cnt].n=h[s];
h[s]=cnt;
}
void Tarjan(int x){
d[x]=l[x]=++sum;
v[x]=1;
s[++top]=x;
for(int i=h[x];i;i=p[i].n){
int y=p[i].t;
if(!d[y]){
Tarjan(y);
l[x]=min(l[x],l[y]);
}
else if(v[y]){
l[x]=min(l[x],d[y]);
}
}
if(l[x]==d[x]){
int y;
num++;
do{
y=s[top--];
v[y]=0;
c[y]=sum;
}while(y!=x);
}
}
bool find(){
for(int i=1;i<=n;i++){
if(c[i]==c[i+n]){
return 0;
}
}
return 1;
}
void make(){
for(int i=1;i<=n;i++){
if(c[i]>c[i+n]){
printf("1 ");
}
else{
printf("0 ");
}
}
}
}G;
int read(){
int i=1,j=0;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-')i=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
j=j*10+ch-48;
ch=getchar();
}
return i*j;
}
void in(){
n=read(),m=read();
int a,b,x,y;
for(int i=1;i<=m;i++){
a=read(),x=read();
b=read(),y=read();
G.add(a+(!x)*n,b+y*n);
G.add(b+(!y)*n,a+x*n);
}
}
void work(){
for(int i=1;i<=2*n;i++){
if(!G.d[i]){
G.Tarjan(i);
}
}
if(G.find()){
puts("POSSIBLE");
return ;
}
puts("IMPOSSIBLE");
exit(0);
}
void out(){
G.make();
}
int main(){
in();
work();
out();
return 0;
}