有 (n) 个变量 (x_1 sim x_n(x_i in {0,1})),另有 (m) 个需要满足的条件,每个条件的形式都是 「(x_i) 为 (1) / (0) 或 (x_j) 为 (0) / (1)」。比如 「(x_1) 为 (0) 或 (x_3) 为 (1)」、「(x_7) 为 (0) 或 (x_2) 为 (0)」。
2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。
(n,m leq 10^6)
上述问题即为一个 2-SAT 问题。这类问题通常是给只有两种取值的变量赋值,给出限制(但种类不仅仅是上题中出现的),要求给出一组合法解。
解决 2-SAT 问题一般转化为图论模型,然后使用图论相关算法求解。
我们考虑把每一个限制转化为若干条边 (i o j),含义是 「选择 (i) 就必须选 (j)」。
因为每个变量有 (0) 和 (1) 两种选择,所以我们将每个变量 (i) 表示为两个图上的点 (i_0,i_1)。其中 (i_0) 表示 (i) 为 (0),(i_1) 表示 (i) 为 (1)。
考虑如何将限制转化为图上的若干条边。
-
一元限制:强制变量 (i) 为 (0) 或 (1)。
以 (i) 为 (0) 举例,则我们需要建边 (i_0 o i_1),含义是 「选择了 (i=0) 就必须选择 (i=1)」,即强制使 (i) 为 (1)。
-
变量 (i) 和变量 (j) 同时为 (0) 或 (1)((i~ ext{or}~j=0) 或 (i ~ ext{and}~j=1))
那么 (i,j) 都为 (0) / (1)。即建立两个一元限制。
-
变量 (i) 为 (1) 或变量 (j) 为 (1)((i~ ext{or}~j=1))
当 (i) 为 (0) 时,(j) 必须为 (1),建边 (i_0 o j_1)。
当 (j) 为 (0) 时,(i) 必须为 (1),建边 (j_0 o i_1)。
-
变量 (i) 为 (0) 或变量 (j) 为 (0)((i~ ext{and}~j=0))
跟上个限制类似,建边 (i_1 o j_0) 和 (j_1 o i_0)。
-
变量 (i) 和变量 (j) 相同((i=j))
注意:这里一共要建 (4) 条边。
当 (i) 为 (0) 时,(j) 必须为 (0),建边 (i_0 o j_0)。
剩余的边类似,(i_1 o j_1,j_0 o i_0,j_1 o i_1)。
-
变量 (i) 和变量 (j) 不同((i eq j))
(i_0 o j_1,i_1 o j_0,j_0 o i_1,j_1 o i_0)。
我们观察到,如果在图上 (i_0) 和 (i_1) 互相可达(即存在从 $i_0 $ 到 (i_1) 的路径,也存在从 (i_1) 到 (i_0) 的路径),那么此问题无解,因为每个变量不可能同时取多个值。
又观察到如果互相可达,则它们在一个强连通分量中。所以求出强连通分量,对于每个 (i_0,i_1) 依次判断即可知道问题是否有可行解。
如果题目要求构造可行解,则将原图缩点成 DAG 后拓扑排序。如果 (i_0) 所在强连通分量的拓扑序比 (i_1) 小,则 DAG 上可能会存在一条 (i_0 o ... o i_1) 的路径。此时如果将 (i) 赋值为 (0),那么从 (i_0) 沿着路径往前走到 (i_1),会发现 (i) 的值必须为 (1),此时矛盾,所以应该将 (i) 赋值为 (1)。
总而言之,如果 (i_x) 的拓扑序小于 (i_{x~ ext{xor}~1}),应该将 (i) 赋值为 (x~ ext{xor}~1)。
小技巧:如果使用 Tarjan 算法,则先找到的强连通分量,在新 DAG 中的拓扑序靠后。在代码实现中将找到的强连通分量依次标号 (col_x),那么当 (col_x < col_y) 时,(x) 的拓扑序比 (y) 大。
回到模板题。此题中的限制都是 (i~ ext{or}~j=1) 类型的,按照上面的方法建边即可。
# include <bits/stdc++.h>
# define rr register
const int N=2000010;
struct Edge{
int to,next;
}edge[N<<2];
int head[N],sum;
std::stack <int> k;
int n,m;
bool vis[N];
int dfn[N],low[N],dfncount,scccount;
int col[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline void add(int x,int y){
edge[++sum].to=y;
edge[sum].next=head[x];
head[x]=sum;
return;
}
void tarjan(int i){
low[i]=dfn[i]=++dfncount;
vis[i]=true;
k.push(i);
for(rr int j=head[i];j;j=edge[j].next){
int to=edge[j].to;
if(!vis[to]&&dfn[to]){
continue;
}
if(!dfn[to]){
tarjan(to);
}
low[i]=std::min(low[i],low[to]);
}
if(low[i]==dfn[i]){
++scccount;
while(k.top()!=i){
int u=k.top();
vis[u]=false,col[u]=scccount,k.pop();
}
vis[i]=false,col[i]=scccount,k.pop();
}
return;
}
inline int id(int a,int b){
return a+n*b;
}
int main(void){
n=read(),m=read();
for(rr int i=1;i<=m;++i){
int u=read(),a=read(),v=read(),b=read();
add(id(u,a^1),id(v,b));
add(id(v,b^1),id(u,a));
}
for(rr int i=1;i<=n*2;++i){
if(!dfn[i]){
tarjan(i);
}
}
for(rr int i=1;i<=n;++i){
if(col[id(i,0)]==col[id(i,1)]){
printf("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for(rr int i=1;i<=n;++i){
printf("%d ",(col[id(i,1)]<col[id(i,0)]));
}
return 0;
}