2-SAT

2-SAT

引入

我们有很多个变量,分别为 x 1 , x 2 , … , x n x_1,x_2,\dots,x_n x1,x2,,xn

每个变量有真假两种情况

我们有几个条件,由几个变量或起来,可以添加非,如:

  • x 1 ∪ x 2 ∪ x 3 x_1∪ x_2∪ x_3 x1x2x3
  • ¬ x 2 ∪ ¬ x 3 ∪ x 4 \neg x_2∪\neg x_3∪ x_4 ¬x2¬x3x4

在SAT问题中,我们求出这些变量的取值,满足许多个这样的条件

然而不幸的是,SAT问题是一个NPC问题,我们没有办法在多项式时间内求出解

不过,对于2-SAT问题,我们有比较好的解法

在2-SAT问题中,我们在条件中只会涉及到两个变量,如:

  • x 1 ∪ x 3 x_1∪ x_3 x1x3
  • ¬ x 3 ∪ x 4 \neg x_3∪ x_4 ¬x3x4

有了这个条件限制,我们可以在 O ( n + m ) O(n+m) O(n+m)的复杂的内求解

比较奇怪是,我们使用tarjan算法求解

例题

【模板】2-SAT 问题

AcWing链接

a ∪ b ⟺ ¬ a ⇒ b    ¬ b ⇒ a a ∪ b\Longleftrightarrow \neg a\Rightarrow b\space \space \neg b \Rightarrow a ab¬ab  ¬ba
于是我们对于 ¬ a → b \neg a\rightarrow b ¬ab建一条有向边, ¬ b → a \neg b \rightarrow a ¬ba建一条有向边

一个例子:

在这里插入图片描述

如何判断无解呢?

对于一个点 x 1 x_1 x1如果我们当前路径经过了 ¬ x 1 \neg x_1 ¬x1,并且沿着 ¬ x 1 \neg x_1 ¬x1走能到达 x 1 x_1 x1

也就是说, x 1 = 1 x_1=1 x1=1时,则 ¬ x 1 \neg x_1 ¬x1必须取1, ¬ x 1 \neg x_1 ¬x1取1,则 x 1 x_1 x1必须取1

因此当变量 x i x_i xi ¬ x i \neg x_i ¬xi处于同一个强连通分量中时无解

因此我们选用tarjan

哪如果每一对 x i x_i xi ¬ x i \neg x_i ¬xi都不在用一个强连通分量中时,是不是一定有解呢?

答案是肯定的

集体来说,我们枚举所有点

我们进行缩点,得到一张拓扑图

我们进行拓扑排序

对于 x i x_i xi ¬ x i \neg x_i ¬xi

我们找到他们各自所在的连通块(缩完点之后的点)

找到他们在拓扑排序中被遍历到的顺序

我们让顺序靠后的连通块成立

就OK了

/*************************************************************************
    > File Name: [模板]2-SAT问题.cpp
    > Author: Typedef 
    > Mail: 1815979752@qq.com 
    > Created Time: 2021/3/4 22:23:14
    > Tags: 2-SAT tarjan
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int N=2000010,M=2000010;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],ts,stk[N],top;
int id[N],cnt;
bool ins[N];
void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u){
	dfn[u]=low[u]=++ts;
	stk[++top]=u,ins[u]=true;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(!dfn[j]){
			tarjan(j);
			low[u]=min(low[u],low[j]);
		}else if(ins[j]) low[u]=min(low[u],dfn[j]);
	}
	if(low[u]==dfn[u]){
		int y;
		cnt++;
		do{
			y=stk[top--],ins[y]=false,id[y]=cnt;
		}while(y!=u);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	memset(h,-1,sizeof(h));
	while(m--){
		int i,a,j,b;
		scanf("%d%d%d%d",&i,&a,&j,&b);
		i--,j--;
		add(2*i+!a,2*j+b);
		add(2*j+!b,2*i+a);
	}
	for(int i=0;i<n*2;i++)
		if(!dfn[i])
			tarjan(i);
	for(int i=0;i<n;i++)
		if(id[i*2]==id[i*2+1]){
			puts("IMPOSSIBLE");
			return 0;
		}
	puts("POSSIBLE");
	for(int i=0;i<n;i++)
		if(id[i*2]<id[2*i+1]) printf("0 ");
		else printf("1 ");
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值