学习资料:
http://www.cnblogs.com/kuangbin/archive/2012/10/05/2712429.html
http://blog.csdn.net/pi9nc/article/details/11849843
2-sat问题描述:
给定n对二元组,给定一定的限定条件,并且对于每一个二元组来说能且仅能选择一个元素,问如何选择才能满足所有的限定条件。如果这个限定条件只对两个元素进行限定,那么就称之为2-sat问题。2-sat问题是存在多项式解法的,而sat问题是NP完全的。
对于2-sat问题,这个可以转换成用布尔方程来表达,要求找出是否能够这个布尔表达是为真。因为二元组只能并且必须选择一个元素,就可以对应成为布尔变量。对于所有的限定条件都要满足,那么可以用与关系将他们连接起来。
解法:
对于每一个限定条件都可以看成是一个布尔公式的子句,整个布尔公式就是一个与关系连接了一系列的子句。对于每一个布尔变量也就是每一个二元组x, 构造两个顶点x和x’。对于每个子句都转换成蕴含关系(=>),比如 a OR b 转换成 (a’ => b) AND (b’ => a) 。
利用蕴含关系来建图,如果图中存在(i,j),意味着如果选择了i,必须要选择j。(这也就是蕴含关系的内容)
既然如此,那么对于图中的同一个强连通分量中的所有元素的布尔值都必须是相同的,也就是不能出现某一个变量x, x 和 x’都在同一个强连通分量中,这样显然没有办法令整个布尔公式为真。
所以要做的就是根据限定条件建图,然后进行强连通分量的分解,记录下原图中每个点所在的强连通分量的序号。
可行解的判断:
所以要进行可行解的判断只需要对于每一个布尔变量x进行判断是否 x 和 x’都在同一个强连通分量中就行了,如果没有这样的布尔变量那么就是存在可行解的。
输出可行解:
对于每一个布尔变量x,如果x所在的强连通分量的拓扑序在x’所在的强连通分量的拓扑序之后,那么x为真的。从而可以构造出一个可行解路径。
常用的限制关系:
xANDy=0<=>(x=>y′)AND(y=>x′)
xANDy=1<=>(x′=>x)AND(y′=>y)
(上面这个比较不好想到, 考虑 x,y都要为1,所以保证只要x或y为0都是假的,将x’连到x,y’连到y,使得推出矛盾)
xORy=0<=>(x=>x′)AND(y=>y′)
xORy=1<=>(x′=>y)AND(y′=>x)
xXORy=0<=>(x′=>y′)AND(x=>y)AND(y=>x)AND(y′=>x′)
xXORy=1<=>(x=>y′)AND(x′=>y)AND(y=>x′)AND(y′=>x)
例题poj3683:
题意:
N对新人要举行婚礼,有一个起始时间Si,结束时间Ti,都有一个用时为Di的特殊仪式。这个需要神父参加,仪式可以在开始时进行也可以在结束时进行。是否能出席所有特别仪式,输出解。
思路:
定义变量xi,如果xi为真表示在开始举行。
对于每个婚礼i和j,假设如果Si ~ Si + Di 与Sj ~ Sj + Dj 有相交的部分,那么就是冲突了,所以xi AND xj = 0,转换成xi’ OR xj’ = 1。按照这样的关系建图就可以了,之后都挺套路的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
using namespace std;
#define M 10009
vector<int> edge[2*M];
int S[M],T[M],D[M];
int DFN[2*M],low[2*M],topo[2*M],belong[2*M];
bool instack[2*M];
int n,sccnum,tot;
stack<int> ss;
void init()
{
tot = 0;
sccnum = 0;
memset(DFN,0,sizeof(DFN));
memset(low,0,sizeof(low));
memset(topo,0,sizeof(topo));
while(!ss.empty()) ss.pop();
}
void add_edge(int u,int v)
{
edge[u].push_back(v);
}
void tarjan(int u)
{
DFN[u] = low[u] = ++tot;
instack[u] = true;
ss.push(u);
for(int i = 0;i < edge[u].size();i++)
{
int v = edge[u][i];
if(!DFN[v])
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
{
low[u] = min(DFN[v],low[u]);
}
}
if(DFN[u] == low[u])
{
int v;
sccnum++;
do
{
v = ss.top();
ss.pop();
instack[v] = false;
belong[v] = sccnum; //topo反序
}while(u != v);
}
}
void scc()
{
for(int i = 0;i < 2*n;i++)
{
if(!DFN[i]) tarjan(i);
}
}
int main()
{
while(scanf("%d",&n) == 1)
{
for(int i = 0;i < n;i++)
{
int a,b,c,d,time;
scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&time);
S[i] = a*60 + b;
T[i] = c*60 + d;
D[i] = time;
}
//0~N-1 x_i N~2*N-1 x_i'
for(int i = 0;i < n;i++)
{
for(int j = 0;j < i;j++)
{
if(S[j] < S[i]+D[i] && S[j]+D[j] > S[i]) //si ~ si + Di,sj ~ sj + Dj
{
add_edge(i,n+j);
add_edge(j,n+i);
}
if(S[i] < T[j] && S[i]+D[i] > T[j]-D[j])//si ~ si + Di, Tj - Dj ~ Tj
{
add_edge(i,j);
add_edge(j+n,i+n);
}
if(T[i]-D[i] < S[j]+D[j] && S[j] < T[i])//Ti - Di ~ Ti, Sj ~ Sj+Dj
{
add_edge(i+n,j+n);
add_edge(j,i);
}
if(T[j]-D[j] < T[i] && T[i]-D[i] < T[j]) // Ti - Di ~ Ti, Tj - Dj ~ Tj
{
add_edge(i+n,j);
add_edge(j+n,i);
}
}
}
scc();
bool ok = false;
for(int i = 0;i < n;i++)
{
if(!ok && belong[i] == belong[i+n])
{
printf("NO\n");
ok = true;
}
}
if(!ok)
{
printf("YES\n");
for(int i = 0;i < n;i++)
{
if(belong[i] < belong[n+i]) // x_i所在强连通分量的拓扑序在 x_i'之后 //因为belong 的标号是拓扑反序,最小的反而是拓扑序大的
{
printf("%02d:%02d %02d:%02d\n",S[i]/60,S[i]%60,(S[i]+D[i])/60,(S[i]+D[i])%60);
}
else
{
printf("%02d:%02d %02d:%02d\n",(T[i]-D[i])/60,(T[i]-D[i])%60,T[i]/60,T[i]%60);
}
}
}
}
return 0;
}