【算法】2-SAT问题

文章介绍了2-SAT问题的概念,它涉及到n个关系集合,每个集合有两个元素,需要满足一定的限制条件。理论思路是通过建立元素间的依赖关系,并转化为图论问题,利用强连通分量和拓扑排序来判断是否存在解以及找到解。提供的代码示例展示了如何构建图并执行深度优先搜索来解决2-SAT问题。
摘要由CSDN通过智能技术生成

前置知识: 强连通分量


什么是 2 − S A T 2-SAT 2SAT ?

2 − S A T 2-SAT 2SAT 算法一般是指有 n n n关系集合,集合中有两个元素,一般必须选其中一个。此外还有若干个限制条件,求可行的方案。


理论思路

【模板】2-SAT 问题 为例。

一个关系集合之中的两个元素,我们用 u u u v v v 来表示。

在这 n n n 个关系集合之中,有 n n n相对关系,一个为 t r u e true true,一个为 f a l s e false false

本题中,相对关系指 x x x 为真或 x x x 为假。

其中,如果我们选了一个元素 u u u,另一个与 u u u 相对的元素 u ′ u' u 我们就不能再选,因为相互冲突。 v v v 也是同理。

因为每个元素都在属于它的含有两个元素的集合之中,且必须选择一个,所以如果没有选 u u u 就必须选 v v v。相反,如果我们选了 u u u,就不必再选 v v v

根据刚刚推导出来的的理论,我们可以得出,如果选择了与 u u u 相对的元素 u ′ u' u,也就必须选择属于 u u u 关系集合中的另一个元素 v v v;如果选择了与 v v v 相对的元素 v ′ v' v,也就必须选择属于 v v v 关系集合中的另一个元素 u u u

那么, 2 − S A T 2-SAT 2SAT 问题的建立规则也就是:选择任意一个元素的相对元素,就必须选择此元素所在集合的另外一个元素。


代码思路

那如何在代码中实现呢?

根据我们上面的结论,可以发现,他们都是属于一种依赖关系。那我们可不可以建成一个图,用来表示如果选择 u u u,则必须选 v v v 呢?

答案是可以的

我们将集合中每一个元素的相对元素向集合中另一个元素连一条有向边,表示选了相对元素就必须选另一个元素。

那我们又如何判断有没有解呢?

我们知道,在任何一个强连通分量当中,无论是那个点,都可以到达此强连通分量当中的任意一点

所以根据这个性质,如果 u u u u ′ u' u 处于同一个强连通分量当中,那 u u u 就依赖于 u ′ u' u 或者是 u ′ u' u 就依赖于 u u u。显然,这与 2 − S A T 2-SAT 2SAT 的定理互相冲突,也就是说明不可能得出一种合法的依赖关系,也就是说此情况无解

那么又如何再查找到此情况的一组解?

在拓扑序中,拓扑序越大遍历的顺序也就越靠后。在强连通分量中, d f s dfs dfs 序越小,也就表示先被缩点,而拓扑序越大,也是先被缩点。所以 d f s dfs dfs 序就是拓扑序的反序

如果 d f s dfs dfs 序越小,拓扑序越大,可能性也就越大

因为强连通分量是一个深搜的过程,在查找的时候,自然也就是拓扑序越大的自然要在后遍历到的强连通分量的缩点缩得更快一些,也就是优先被处理了出来,所以自然可能性更要大一些。

贴代码

#include<bits/stdc++.h>
using namespace std;
struct edge{
	int to,next;
}ed[10000001];
int he[10000001],tot;
int dfn[10000001],idx;
int low[10000001];
bool vis[10000001];
int color[10000001],kind;
stack<int>s;
int n,m;
void insert(int u,int v)
{
	tot++;
	ed[tot].to=v;
	ed[tot].next=he[u];
	he[u]=tot;
}
void tarjan(int u)
{
	low[u]=dfn[u]=++idx;
	s.push(u);vis[u]=1;
	for(int i=he[u];i!=-1;i=ed[i].next)
	{
		int v=ed[i].to;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])
			low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		int x;
		kind++;
		do
		{
			x=s.top();s.pop();
			vis[x]=0;
			color[x]=kind;
		}while(x!=u);
	}
}
int main()
{
	memset(he,-1,sizeof(he));
	scanf("%d %d",&n,&m);
	while(m--)
	{
		int u,x,v,y;
		scanf("%d %d %d %d",&u,&x,&v,&y);
		if(x&&y)
		{
			insert(u+n,v);
			insert(v+n,u);
		}
		if(x&&!y)
		{
			insert(u+n,v+n);
			insert(v,u);
		}
		if(!x&&y)
		{
			insert(u,v);
			insert(v+n,u+n);
		}
		if(!x&&!y)
		{
			insert(v,u+n);
			insert(u,v+n);
		}
	}
	for(int i=1;i<=n*2;i++)
		if(!dfn[i]) tarjan(i);
	for(int u=1;u<=n;u++)
		if(color[u]==color[u+n])
			{puts("IMPOSSIBLE");return 0;}
	puts("POSSIBLE");
	for(int i=1;i<=n;i++)
	{
		if(color[i]<color[i+n]) printf("1 ");
		else printf("0 ");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值