题目背景
2-SAT 问题 模板
题目描述
有n个布尔变量 x1x_1x1 ~ xnx_nxn ,另有m个需要满足的条件,每个条件的形式都是“ xix_ixi 为true/false或 xjx_jxj 为true/false”。比如“ x1x_1x1 为真或 x3x_3x3 为假”、“ x7x_7x7 为假或 x2x_2x2 为假”。2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
输入输出格式
输入格式:
第一行两个整数n和m,意义如体面所述。
接下来m行每行4个整数 i a j b,表示“ xix_ixi 为a或 xjx_jxj 为b”(a,b∈{0,1})
输出格式:
如无解,输出“IMPOSSIBLE”(不带引号); 否则输出”POSSIBLE”(不带引号),下 一行n个整数 x1x_1x1 ~ xnx_nxn ( xix_ixi ∈{0,1}),表示构造出的解。
输入输出样例
输入样例#1:
3 1
1 1 3 0
输出样例#1:
POSSIBLE
0 0 0
解题思路
2-SAT 模板题,将i拆成i*2与i*2+1两个点。对于要么a,要么b的情况让a向b的另一个点连边,b向a的另一个点连边,然后tarjan求出强联通分量,在同一个强联通分量的必须选。如果a与a拆的点在同一个强联通分量里,就证明矛盾。输出方案时要利用tarjan后图的性质,是一个逆拓扑排序,然后对于一个点拆成的两个点选出拓扑序更大的。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN = 1e6+5;
inline int rd(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {f=ch=='-'?-1:1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int n,m,head[MAXN<<1],cnt;
int to[MAXN<<1],nxt[MAXN<<1];
int bl[MAXN<<1],col_num,stk[MAXN<<1],top;
int dfn[MAXN<<1],low[MAXN<<1],num;
bool vis[MAXN<<1],flag;
inline void add(int bg,int ed){
to[++cnt]=ed,nxt[cnt]=head[bg],head[bg]=cnt;
}
void tarjan(int x){
dfn[x]=low[x]=++num;
stk[++top]=x;vis[x]=1;
for(register int i=head[x];i;i=nxt[i]){
int u=to[i];
if(!dfn[u]) {
tarjan(u);
low[x]=min(low[x],low[u]);
}
else if(vis[u]) low[x]=min(low[x],dfn[u]);
}
if(low[x]==dfn[x]){
bl[x]=++col_num;vis[x]=0;
while(stk[top]!=x){
vis[stk[top]]=0;
bl[stk[top--]]=col_num;
}top--;
}
}
int main(){
n=rd();m=rd();
for(register int i=1;i<=m;i++){
int x=rd(),sx=rd(),y=rd(),sy=rd();
int xt=sx^1,yt=sy^1;
add(x<<1|xt,y<<1|sy);
add(y<<1|yt,x<<1|sx);
}
for(register int i=2;i<=(n<<1|1);i++) if(!dfn[i]) tarjan(i);
for(register int i=1;i<=n;i++)
if(bl[i<<1]==bl[i<<1|1]) {flag=1;break;}
if(flag) puts("IMPOSSIBLE");
else{
puts("POSSIBLE");
for(register int i=1;i<=n;i++)
printf("%d ",bl[i<<1]>bl[i<<1|1]);
}
}