学习笔记第二十八节:2-SAT

正题

      我又来划水了。

      2-SAT问题是类似于这样的形式:

      给出n个数\in 0\ or \ 1,m组条件,问你这m组条件是否能同时满足。

      每组条件类似于这样的形式(x=a\ |\ y=b)[a,b\in 0\ or\ 1]

      怎么做?

      我们可以拆点!

      把每个点拆成两个点,一个表示选,一个表示不选。

      那么假如有一个这样一个条件:x=0\ |\ y=0

      考虑x取1时,y必须取0;y取1时,x必须取0.

      所以我们建两条边:(x_1,y_0),(y_1,x_0)

      表示的就是上面的意思。

      接下来,让我们透彻理解边的意思。

      边的意思就是,如果a满足,b必须满足。

      显然x_0,x_1不可能同时取。

      在图中怎么表示呢?!!原来是强联通分量!首先,强联通分量的要么同时取要么同时不取。

      所以如果x_0,x_1在同一强联通分量,那么很明显是矛盾的。如果有,输出Impossible。

      否则就肯定存在一组解。a\to b有路径,那么a取,b必须取。

      对于x_0,x_1如果在一个联通分量,那么肯定取拓扑序大的,为什么?因为如果取拓扑序小的,那么拓扑序大的那个可能也会取到,就会矛盾,为了避免意外,我们选择拓扑序大的。

      如果不在一个连通分量,那么我们取那个都无所谓。

      所以,取拓扑序大的就可以了。(当然这里的拓扑序是缩点后的拓扑序

      最后,因为Tarjan是深搜,搜出来的拓扑序一定是倒序的,所以写的时候选小的。

      20190813upd:这个2-SAT不是严格2-SAT问题,这种问题要满足一个条件:交换律。

      也就是说,若x_0,y_0不能同时取,那么y_0,x_0不能同时取。这个问题有时候会在很多问题中很麻烦。

      那么就不存在一个状态x_0可以到达另外两个状态y_0,y_1,因为那样的话,y_0,y_1都必然到x_1。那么x_0,x_1就必然在同一个强连通分量里面。无解。

       还有很多很多的细节,但是总是可以被证明出来:在这种不严格的2-sat的形式中,按照标准做法总是可以解决。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<stack>
using namespace std;

int n,m;
struct edge{
	int y,next;
}s[2000010];
int first[2000010],len=0;
int t=0,T=0;
int dfn[2000010],low[2000010],tim[2000010];
bool tf[2000010];
stack<int> f;

void ins(int x,int y){
	len++;
	s[len]=(edge){y,first[x]};first[x]=len;
}

void Tarjan(int x){
	dfn[x]=low[x]=++t;
	f.push(x);tf[x]=true;
	for(int i=first[x];i!=0;i=s[i].next){
		int y=s[i].y;
		if(!dfn[y]){
			Tarjan(y);
			low[x]=min(low[y],low[x]);
		}
		else if(tf[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		T++;
		int d;
		tim[x]=T;
		tf[x]=false;
		while((d=f.top())!=x){
			f.pop();
			tim[d]=T;
			tf[d]=false;
		}
		f.pop();
	}
}

int main(){
	scanf("%d %d",&n,&m);
	int x,a,y,b;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d %d",&x,&a,&y,&b);
		if(a==0 && b==0) ins(x,y+n),ins(y,x+n);
		if(a==0 && b==1) ins(x,y),ins(y+n,x+n);
		if(a==1 && b==0) ins(x+n,y+n),ins(y,x);
		if(a==1 && b==1) ins(x+n,y),ins(y+n,x);
	}
	for(int i=1;i<=2*n;i++)	
		if(!dfn[i]) Tarjan(i);
	for(int i=1;i<=n;i++)
		if(tim[i]==tim[i+n]){
			printf("IMPOSSIBLE");
			return 0;
		}
	printf("POSSIBLE\n");
	for(int i=1;i<=n;i++)
		printf("%d ",tim[i]<tim[i+n]);
	printf("\n");
}

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值