给定一个布尔方程,判断是否存在一组布尔变量的真值指派是整个方程为真。
当布尔方程为合取范式,且每个字句的文字为两个时,称为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;
}