(模板)2-SAT

(模板)2-SAT

题目描述

有n个变量,m条语句,没个形如:xi为a或xj为b(a,b=0或1)...

判断是否有解,有的话构造出来一组解

思路:

  • 将每个变量分为两个点,取0和取1(i和i+n)
  • 对于每条语句可以转换为若xi取(1-a)则xj必须取b,若xj取(1-b)则xi必须取a,按照这样的关系连边
  • 跑一遍tarjan,则在同一个强连通分量中如果取了一个点,那么一定要取其中的其他的点,如果i与i+n在同一个强连通分量,则一定无解
  • 有解时,先缩点,然后每次找到出度为0的点,选取它,并删除即可,这样得到的操作序列就相当于倒着跑一遍拓扑排序(因为拓扑排序是先找入度为0的点)
  • 又注意到tarjan后的col数组其实就是一个自底向上的拓扑序,所以我们每次选出度最小的点就相当于选col小的点

代码:

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

#define res register int
inline int read()
{
	int x=0,f=1; char ch;
	while(!isdigit(ch=getchar())) if(ch=='-') f=-1;
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}

const int N=4e6+5,M=4e6+5;
int head[N],fron[M],ver[M],nxt[M];
int tot,n,m;

inline void add(int x,int y)
{
	ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}

int dfn[N],low[N],col[N],colnum,num;
bool ins[N];
stack <int> s;

void tarjian(int x)
{
	dfn[x]=low[x]=++num;
	s.push(x); ins[x]=1;
	for(res i=head[x],y ; i ; i=nxt[i])
		if(!dfn[y=ver[i]])
			tarjian(y),low[x]=min(low[x],low[y]);
		else 
			if(ins[y])
				low[x]=min(low[x],dfn[y]);
	if(dfn[x]==low[x])
	{
		++colnum; int y;
		do { 
			y=s.top(); s.pop();
			ins[y]=0; col[y]=colnum;
		}while(x!=y);
	}
}

// xi=xj-> yi=yj

int opp[N];
inline void work()
{
	for(res i=1 ; i<=n*2 ; ++i)
		if(!dfn[i]) tarjian(i);
		
	for(res i=1 ; i<=n ; ++i) 
	{
		if(col[i]==col[n+i]) 
		{
			puts("IMPOSSIBLE"); 
			return;
		}
	}
	puts("POSSIBLE");
	for(res i=1 ; i<=n ; ++i)
		printf("%d ", col[i] > col[i+n]);//col[i]<col[i+n]时选i->取0
}

int main()
{
	n=read(); m=read();
	for(res i=1 ; i<=m ; ++i)
	{
		int a=read(),b=read(),c=read(),d=read();
		// a=b^1 -> c=d
		// c=d^1 -> a=b
		add(a+(b^1)*n,c+d*n); add(c+(d^1)*n,a+b*n);
	}
	work();
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/wmq12138/p/10582446.html