摘自http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html
现有一个由N个布尔值组成的序列A,给出一些限制关系,比如A[x] && A[y] = 0、A[x] || A[y] || A[z]=1等,要确定A[0..N-1]的值,使得其满足所有限制关系。这个称为SAT问题,特别的,若每种限制关系中最多只对两个元素进行限制,则称为2-SAT问题。
由于在2-SAT问题中,最多只对两个元素进行限制,所以可能的限制关系共有11种:
A[x]
NOT A[x]
A[x] AND A[y]
A[x] AND NOT A[y]
A[x] OR A[y]
A[x] OR NOT A[y]
NOT (A[x] AND A[y])
NOT (A[x] OR A[y])
A[x] XOR A[y]
NOT (A[x] XOR A[y])
A[x] XOR NOT A[y]
进一步,A[x] AND A[y]相当于(A[x]) AND (A[y])(也就是可以拆分成A[x]与A[y]两个限制关系),NOT(A[x] OR A[y])相当于NOT A[x] AND NOT A[y](也就是可以拆分成NOT A[x]与NOT A[y]两个限制关系)。因此,可能的限制关系最多只有9种。
2-SAT问题在大多数时候表现成以下形式:有N对物品,每对物品中必须选取一个,也只能选取一个,并且它们之间存在某些限制关系(如某两个物品不能都选,某两个物品不能都不选,某两个物品必须且只能选一个,某个物品必选)等,这时,可以将每对物品当成一个布尔值(选取第一个物品相当于0,选取第二个相当于1),如果所有的限制关系最多只对两个物品进行限制,则它们都可以转化成9种基本限制关系,从而转化为2-SAT模型。
【建模】
可以构造有向图G,G中包含2*N个顶点,前N个顶点(1~N)表示第i个元素能被选择,后N个顶点(N+1~2*N)表示第i个元素不能被选择。Ai和A(i+N)不能同时被选择。同理Ai + Bj = ~( A(i+N) + B(j+N) ),A(i+N)和B(j+N)不能同时被选。选中~A,必须选择B;如果选择~B,必须选择A。
若图中i到j有路径,则若i选,则j也要选;或者说,若j不选,则i也不能选。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int MAXN = 2200;
const int MAXM = MAXN*MAXN;
struct EdgeNode
{
int to;
int next;
}Edges[MAXM];
int Head[MAXN];
int dfn[MAXN],low[MAXN],belong[MAXN],Stack[MAXN],vis[MAXN];
int m,id,lay,scc,N,M;
//belong[]来判断i和i+N是否在一个强连通分量里
void AddEdges(int u,int v)
{
Edges[id].to = v;
Edges[id].next = Head[u];
Head[u] = id++;
}
void TarBFS(int pos)
{
dfn[pos] = low[pos] = ++lay;
Stack[m++] = pos;
vis[pos] = 1;
for(int i = Head[pos]; i != -1; i = Edges[i].next)
{
int v = Edges[i].to;
if( !dfn[v] )
{
TarBFS(v);
low[pos] = min(low[pos],low[v]);
}
else if(vis[v])
low[pos] = min(low[pos],low[v]);
}
int v;
if(dfn[pos] == low[pos])
{
++scc;
do
{
v = Stack[--m];
belong[v] = scc;
vis[v] = 0;
}while(v != pos);
}
}
int main()
{
int u,v;
while(~scanf("%d%d",&N,&M))
{
id = m = scc = lay = 0;
memset(Head,-1,sizeof(Head));
memset(vis,0,sizeof(vis));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(belong,0,sizeof(belong));
for(int i = 0; i < M; ++i)
{
scanf("%d%d",&u,&v);
int a = abs(u);
int b = abs(v);
if(u > 0 && v > 0)
{ //a、b至少一个被选中
AddEdges(a+N,b); //如果a不被选,b就必须被选
AddEdges(b+N,a); //如果b不被选,a就必须被选
}
if(u < 0 && v < 0)
{ //a、b至少有一个不被选中
AddEdges(a,b+N); //如果a被选,b就必须不被选
AddEdges(b,a+N); //如果b被选,a就必须不被选
}
if(u > 0 && v < 0)
{ //a被选中和b不被选中两件事至少发生一件
AddEdges(a+N,b+N); //如果a不被选中,b必须不被选中
AddEdges(b,a); //如果b被选中,那么a必须被选中
}
if(u < 0 && v > 0)
{ //a不被选中和b被选中至少发生一件
AddEdges(a,b); //如果a被选中,b必须被选中
AddEdges(b+N,a+N); //如果b不被选中,a必须不被选中
}
}
for(int i = 1; i <= 2*N; ++i)
if( !dfn[i] )
TarBFS(i);
int ans = 1;
for(int i = 1; i <= N; ++i)
{
if(belong[i] == belong[i+N]) //如果i和i+N同在一个连通分量里,则2-SAT不满足
{
ans = 0;
break;
}
}
printf("%d\n",ans); //不存在就满足2-SAT
}
return 0;
}