学习笔记:2-SAT问题

前言

最后一篇学习笔记了,之后就要咕咕咕很久了。在机房的最后一天,祝好。

原理

传送门luoguP4782
解释一下题目:
根据要求将布尔变量赋值,如1 1 3 0就是说要求将变量1为1或者变量3为0。
由于每个布尔变量只有两个值1,0。我们可以将其拆开,由此我们发现:

对于变量x,y,要求x或y要满足的值为a,b
当a=1,b=1时,如果x=0,那么一定推出y必须为1,如果y=0,同理推出x=1。
当a=1,b=0时,如果x=0,那么一定推出y必须为0,如果y=1,同理推出x=1。
当a=0,b=1时,如果x=1,那么y=1,如果y=0,那么x=0。
当a=0,b=0时,如果x=1,那么y=0,如果y=1,那么x=1。

将拆的点分为1~n表示0的情况,n+1~2n表示1的情况,通过上面的推导,我们就将这些一定能推出的连边,最后变成了一张图:
例如:
有2个变量时,要满足,
a=0,b=1;a=1,b=1;a=0,b=0,建出图就是:
在这里插入图片描述

我们发现x和!y在一个强连通里面,!x和y在一个强连通里面。找强连通通常用Tarjan算法,
然后我们就可以缩点,变成有向无环图DAG。
在这里插入图片描述
其中x和!y在强连通1中,!x和y在强连通2中,
那我们要如何赋值呢?
当x赋的1时,即在1里,此时通过x可以推出!y一定成立,!y成立,那么!x也一定成立,就相互矛盾了,当x赋为0时,仅推出!y也必须成立。

故此我们容易想到拓扑排序,我们应当要选择x和!x中拓扑序较大的点来作为值。
而Tarjan算法中dfs已经搞出了每个强连通的dfs序,
由于dfs序和拓扑序正好相反,我们只需反着来,选择x和!x在强连通编号较小的,就是拓扑序较大点来作为值。

那么如果没有解的情况呢?
如果x和!x在同一强连通里面,无论怎样赋值,x和!x总能互相推出,总会矛盾,所以这就是无解的情况,即x和!x在强连通的编号相同。

值得注意的是:
跑Tarjan时要枚举2n个点,是否dfn为0。
代码实现:

#include<bits/stdc++.h>
using namespace std;

const int M=4e6+5;
int n,m;
bool vis[M];
int dfn[M],id[M],low[M],cnt,sum;
int first[M],nex[M],to[M],tot;
stack<int>q;

void add(int x,int y){
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
}

void dfs(int x){
	dfn[x]=low[x]=++cnt;
	q.push(x);vis[x]=1;
	for(int i=first[x];i;i=nex[i]){
		int y=to[i];
		if(dfn[y]==0){
			dfs(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}
	if(low[x]==dfn[x]){
		sum++;
		int k;
		do{
			k=q.top();
			q.pop();
			id[k]=sum;
			vis[k]=0;
		}while(k!=x);
	}
}

int main(){
	scanf("%d%d",&n,&m);
	int x,y,a,b;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&x,&a,&y,&b);
		if(a==0 && b==0){
			add(x+n,y);
			add(y+n,x);
		}
		if(a==1 && b==0){
			add(x,y);
			add(y+n,x+n);
		}
		if(a==0 && b==1){
			add(y,x);
			add(x+n,y+n);
		}
		if(a==1 && b==1){
			add(x,y+n);
			add(y,x+n);
		}
	}
	for(int i=1;i<=2*n;i++)
		if(dfn[i]==0) dfs(i);
		
	for(int i=1;i<=n;i++)
		if(id[i]==id[i+n]) {cout<<"IMPOSSIBLE";return 0;}
	cout<<"POSSIBLE"<<endl;
	for(int i=1;i<=n;i++)
		if(id[i]>id[i+n]) cout<<1<<" ";
		else cout<<0<<" ";
	return 0;
}

双倍经验,传送门luoguP4171
仅需注意读入问题即可。

#include<bits/stdc++.h>
using namespace std;

const int M=4e6+5;
int n,m,t;
bool vis[M];
int dfn[M],id[M],low[M],cnt,sum;
int first[M],nex[M],to[M],tot;
stack<int>q;

void add(int x,int y){
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
}

void dfs(int x){
	dfn[x]=low[x]=++cnt;
	q.push(x);vis[x]=1;
	for(int i=first[x];i;i=nex[i]){
		int y=to[i];
		if(dfn[y]==0){
			dfs(y);
			low[x]=min(low[x],low[y]);
		}
		else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}
	if(low[x]==dfn[x]){
		sum++;
		int k;
		do{
			k=q.top();
			q.pop();
			id[k]=sum;
			vis[k]=0;
		}while(k!=x);
	}
}

int main(){
	scanf("%d",&t);
	while(t--){
		memset(vis,0,sizeof(vis));
		memset(first,0,sizeof(first));
		memset(dfn,0,sizeof(dfn));
		tot=0;sum=0;cnt=0;
		while(!q.empty()) q.pop();
		
		scanf("%d%d",&n,&m);
		char s1[20],s2[20];
		for(int i=1;i<=m;i++){
			int x=0,y=0,res;
			cin>>s1>>s2;
			res=1;while(isdigit(s1[res])) x=x*10+s1[res++]-'0';
			res=1;while(isdigit(s2[res])) y=y*10+s2[res++]-'0';
			int a=(s1[0]=='m')?1:0;
			int b=(s2[0]=='m')?1:0;
			if(a==0 && b==0){
				add(x+n,y);
				add(y+n,x);
			}
			if(a==1 && b==0){
				add(x,y);
				add(y+n,x+n);
			}
			if(a==0 && b==1){
				add(y,x);
				add(x+n,y+n);
			}
			if(a==1 && b==1){
				add(x,y+n);
				add(y,x+n);
			}
		}
		for(int i=1;i<=2*n;i++)
			if(dfn[i]==0) dfs(i);
		int flag=0;
		for(int i=1;i<=n;i++)
			if(id[i]==id[i+n]){
				cout<<"BAD"<<"\n";
				flag=1;
				break;	
			}
		if(!flag)
		cout<<"GOOD"<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值