2-SAT

给定一个布尔方程,判断是否存在一组布尔变量的真值指派是整个方程为真。
当布尔方程为合取范式,且每个字句的文字为两个时,称为2-SAT
如:(a || b)&& (b || c) && (a || d) …

//复杂度为O(n+m)
#include <cstdio>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
#define maxn 2000010

vector<int> g[maxn];   //若x有一条边连着y,则说明x=>y 
int dfn[maxn],low[maxn],vis[maxn],cnt = 0;
int scc[maxn],c = 1;
stack<int> s;
int n;

int reverse(int x)   //取反操作 
{
	if( x > n ) x -= n;
	else x += n;
	return x;
}

void tarjan(int x)
{
	dfn[x] = low[x] = ++ cnt;
	vis[x] = 1;
	s.push(x);
	for (int i = 0; i < g[x].size(); i++)
	{
		int t = g[x][i];
		if( !dfn[t] )
		{
			tarjan(t);
			low[x] = min(low[x],low[t]);
		}else if( vis[t] )
		{
			low[x] = min(low[x],low[t]);
		}
	}  
	if( dfn[x] == low[x] )
	{
		scc[x] = c;
		vis[x] = 0;
		while( s.top() != x )
		{
			vis[s.top()] = 0;
			scc[s.top()] = c;
			s.pop();
		}
		c ++;
		s.pop();
	}
}

int main()
{
	int m;
	scanf("%d%d",&n,&m);
	for (int i = 0; i < m; i++)
	{
		int x,kind1,y,kind2;
		scanf("%d%d%d%d",&x,&kind1,&y,&kind2);
		if( kind1 == 0 ) x += n;   //x为0则~x为1 
		if( kind2 == 0 ) y += n;
		// ( x || y ) <=> ( ~x => y ) && ( ~y => x )   
		g[reverse(x)].push_back(y);
		g[reverse(y)].push_back(x);  
	}
	for (int i = 1; i <= 2*n; i++)
	{
		if( !dfn[i] ) tarjan(i);   //找强联通,并标注上缩点后的拓扑序 
	}
	for (int i = 1; i <= n; i++)
	{
		if( scc[i] == scc[i+n] )   //i与~i在同一个强联通则无解 
		{
			printf("IMPOSSIBLE\n");
			return 0;
		}
	}
	printf("POSSIBLE\n");
	//相同连通块中的真值相同 
	//若x与~x连通,则我们选择拓扑序大的置为1 
	//设x有一条路径到~x,且将x置为1,那么~x与x的真值一定要相同,导致矛盾
	//若x与~x不连通,则x可任意取值,但也要考虑到相同连通块的情况 
	for (int i = 1; i <= n; i++)   //scc[i]越小,表示拓扑序越大 
	{ 
		//scc[i]的拓扑序大于~i时才可置为1 
		if( i != n ) printf("%d ",scc[i] < scc[i+n]);   
		else printf("%d\n",scc[i] < scc[i+n]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值