[洛谷P4782][题解]2-SAT问题

题目

0.定义

先定义\(k-SAT\)问题:
给出\(n\)个数和\(m\)个形如\(x_1\oplus x_2\oplus\cdots\oplus x_k=0/1\)的关系式(\(\oplus\)\(\land ,\lor\)等运算符),询问是否有解,如果有解,求出\(x_1,x_2,\cdots ,x_k\)的值。
\(k>2\)的已经证明是\(NPC\)了,我们只考虑\(2-SAT\)问题

1.解法

我们已经知道问题是什么样了,接下来该考虑怎么求了
题中给的关系都不确定,我们考虑如何转成确定的关系
遇到这类问题我们常常考虑拆点,那么不妨也在这题试试:
把每个点\(i\)拆成\(i,\lnot i\),然后考虑连边
这里我们举一个很好理解的例子:\(i\land j=1\),求\(i,j\)
第一种情况:\(i=0\)
此时\(j\)只能等于1,于是从\(\lnot i\)连到\(j\)
第二种情况:\(j=0\)
此时\(i\)只能等于1,于是从\(\lnot j\)连到\(i\)
剩下的情况可以自己推一下,代码中用了一个比较方便的位运算简化版
然后呢?
首先判断是否有解
可以很显然地想到:存在点\(i\)\(\lnot i\)在一个强连通分量里\(\Leftrightarrow\)无解
这句话的意思就是如果有一个点为1,但是经过一些推导之后得到该点为0,那么就无解
那么如果有解的话我们怎么求出这个解呢?
直接亮出正解:选择\(i,\lnot i\)中拓扑序较大的点
因为\(Tarjan\)是回溯的时候记录,所以要输出编号小的

2.代码

省略了缺省源

#define N 1000000 
#define M 2000010
int n,m,dfn[M],low[M],bel[M],ins[M],tim,num;
struct Edge {
	int nxt,to;
}e[M];
int head[M],cnt;
inline void ade(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
stack<int>s;//以下为Tarjan板子 
void Tarjan(int now){
	dfn[now]=low[now]=++tim;
	s.push(now),ins[now]=1;
	for(rg int i=head[now];i;i=e[i].nxt){
		int v=e[i].to;
		if(!dfn[v]){
			Tarjan(v);
			low[now]=min(low[now],low[v]);
		}else if(ins[v]){
			low[now]=min(low[now],dfn[v]);
		}
	}
	if(dfn[now]==low[now]){
		num++;
		do {
			bel[now]=num;
			now=s.top();
			s.pop(),ins[now]=0;
		}while(dfn[now]!=low[now]);
	}
}
int main(){
	Read(n),Read(m);
	for(rg int i=1;i<=m;i++){
		int a,b,ba,bb;
		Read(a),Read(ba),Read(b),Read(bb);
		ade(a+n*(ba&1),b+n*(bb^1));//小小的位运算简化代码 
		ade(b+n*(bb&1),a+n*(ba^1));
	}
	for(rg int i=1;i<=(n<<1);i++){
		if(!dfn[i])Tarjan(i);
	}
	for(rg int i=1;i<=n;i++){
		if(bel[i]==bel[i+n]){//判断无解 
			cout<<"IMPOSSIBLE"<<endl;
			return 0;
		}
	}
	cout<<"POSSIBLE"<<endl;
	for(rg int i=1;i<=n;i++){//注意要用小于号 
		cout<<(bel[i]<bel[i+n])<<" ";
	}
	return 0;
}

3.完结撒花

posted @ 2020-04-18 14:21  ajthreac  阅读(162)  评论(1编辑  收藏  举报