2-SAT

2-SAT

概念

SAT是适定性(Satisfiability)问题的简称 。一般形式为k-适定性问题,简称 k − S A T k-SAT kSAT
可以证明,当 k > 2 k>2 k>2时, k − S A T k-SAT kSAT是NP完全的。因此一般讨论的是 k = 2 k=2 k=2的情况,即 2 − S A T 2-SAT 2SAT问题。

我们通俗的说,就是给你 n n n个变量 a i a_i ai,每个变量能且只能取 0 / 1 0/1 0/1的值。同时给出若干条件,形式诸如( n o t ) a i o p t ( n o t ) a j = 0 / 1 not)a_iopt(not) a_j=0/1 not)aiopt(not)aj=0/1,其中 o p t opt opt表示 a n d , o r , x o r and,or,xor and,or,xor中的一种

而求解2-SAT的解就是求出满足所有限制的一组 a a a

解法

首先我们考虑将 2 − S A T 2-SAT 2SAT问题往图论的方向靠,我们发现每个点要么取 0 0 0,要么取 1 1 1。因此对于 a i a_i ai,我们建两个点 2 i − 1 2i−1 2i1 2 i 2i 2i分别表示 a i a_i ai 0 0 0 1 1 1

然后我们考虑建边来表示这些关系,我们令一条有向边的意义: x → y x→y xy表示如果选择了 x x x就必须选 y y y

那么我们可以举一些简单的例子来总结下连边的规律(用 i ′ i′ i表示 i i i的反面):

i , j i,j i,j不能同时选:选了 i i i就要选 j ′ j′ j,选 j j j就要选 i ′ i′ i。故 i → j ′ , j → i ′ i→j′,j→i′ ij,ji。一般操作即为 a i x o r a j = 1 a_i xor a_j=1 aixoraj=1
i , j i,j i,j必须同时选:选了 i i i就要选 j j j,选 j j j就要选 i i i。故 i → j , j → i i→j,j→i ij,ji。一般操作即为 a i x o r a j = 0 a_ixora_j=0 aixoraj=0
i , j i,j i,j任选(但至少选一个)选一个:选了 i i i就要选 j ′ j′ j,选 j j j就要选 i ′ i′ i,选 i ′ i′ i就要选 j j j,选 j ′ j′ j就要选 i i i。故 i → j ′ , j → i ′ , i ′ → j , j ′ → i i→j′,j→i′,i′→j,j′→i ij,ji,ij,ji一般操作即为 a i o r a j = 1 a_iora_j=1 aioraj=1
i必须选:直接i′→i,可以保证无论怎样都选 i i i。一般操作为给出的 a i = 1 a_i=1 ai=1 a i a n d a j = 1 a_ianda_j=1 aiandaj=1
建好图然后就是考虑怎么用图论的方式解决 2 − S A T 2-SAT 2SAT了。

一,显然, D F S DFS DFS
1,对于每个当前不确定的变量 a i a_i ai,令 a i = 0 a_i=0 ai=0然后沿着边 D F S DFS DFS访问相连的点。
2,检查如果会导致任意一个j与j′都被选,那么撤销。否则令 a i = 0 a_i=0 ai=0
3,否则令 a i = 1 a_i=1 ai=1,重复 2 2 2。如果还不行就无解。
4,继续考虑下一个不确定的变量

这样的话正确性显然,由于这里的 D F S DFS DFS涉及到全局,因此复杂度是 O ( n ( n + m ) ) O(n(n+m)) O(n(n+m))的。
一般情况下已经很优秀了,而且还可以改进:
只需要在DFS之前判断i′能否走到i就可以省略撤销标记的过程,所以我们可以bitset优化传递闭包做到 O ( n 3 / w ) O({n^3}/{w)} O(n3/w)预处理,然后就可以 O ( n + m ) O(n+m) O(n+m)的DFS了。

这种做法还可以保证解的字典序,有时不失为一种不错的方法。

二,连通分量
考虑我们上面的判断有无解的情况,我们想到完全可以借助SCC来判断两个点是否互相到达。
那么我们先缩点,如果 i i i i ′ i′ i在同一SCC里那么显然无解。
否则选择 i i i i ′ i′ i中拓扑序较大的一个就可以得到一组可行解。
不是很常用(主要是一般题目的数据范围都不需要),用来判可行性比较好。

代码实现(模板题:SCC)

#include<bits/stdc++.h>
#define M 4000009 
using namespace std;
int nxt[M],first[M],to[M],_first[M],_nxt[M],_to[M],tot;
int n,m,vis[M],turn[M],cnt,bel[M];
int read(){
	char ch;
	int f=1,re=0;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();}
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
void add(int x,int y){
	nxt[++tot]=first[x],first[x]=tot,to[tot]=y;
	_nxt[++tot]=_first[y],_first[y]=tot,_to[tot]=x;
}
void dfs1(int u){
	vis[u]=1;
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];
		if(!vis[v]) dfs1(v);
	}turn[++cnt]=u;
}
void dfs2(int u){
	bel[u]=cnt,vis[u]=1;
	for(int i=_first[u];i;i=_nxt[i]){
		int v=_to[i];
		if(!vis[v]) dfs2(v);
	}
}
void solve(){//Kosaraju算法:一种找强连通分量的算法 
	for(int i=1;i<=n*2;i++) if(!vis[i]) dfs1(i);
	memset(vis,0,sizeof(vis)),cnt=0;
	for(int i=2*n;i>=1;i--) if(!vis[turn[i]]) cnt++,dfs2(turn[i]);
}
int main(){
	int x,y,fx,fy;
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		x=read(),fx=read(),y=read(),fy=read();
		add(x+n*(fx&1),y+n*(fy^1));
		add(y+n*(fy&1),x+n*(fx^1));
	}solve();
	for(int i=1;i<=n;i++) if(bel[i]==bel[i+n]){printf("IMPOSSIBLE\n");return 0;}
	printf("POSSIBLE\n");
	for(int i=1;i<=n;i++) printf("%d ",bel[i]>bel[i+n]);
	return 0;
} 

//Kosaraju算法:一种找强连通分量的算法 

例题

HDU3062
HDU1814
HDU3062
HDU1814
洛谷P3007

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值