2-sat—判可行性及求方案

介绍

2-sat问题的全称是2-satisfied,具体来说,给出n个变量,每个变量有两种取值方案Ai,0和Ai,1。另外有一些限制形如“若Ai选择Ai,p,那么Aj必须选择Aj,q”。求有无解及其方案。

可行性

解决2-sat的问题先要进行模型转换。
对于限制条件“若Ai选择Ai,p,那么Aj必须选择Aj,q”,则从id(i,p)到id(j,q)连一条有向边,表示选择id(i,p)就要选id(j,q)。
注意上述限制条件的逆否命题“若Aj选择Aj,1-q,那么Ai必须选择Ai,1-p”也是成立的,所以还要从id(j,1-q)到(i,1-p)连一条有向边。
举个例子,若有a&b=0,那么有限制条件“若a为1,则b必须为0”,其逆否命题“若b为1,则a必须为0”也成立。而否命题“若a为0,则b必须为1”,和逆命题“若b为0,则a必须为1”则不一定正确。
言归正传,限制条件已经表现为一条路径,即选择某一个点,从该点起的路径上的所有点都必须选择。此时考虑无解情况,用人话说就是“如果Ai选Ai,0,那么Ai必须选Ai,1”,在2-sat中表现为Ai,0出发能去到Ai,1,且Ai,1出发能去到Ai,0,即Ai,0和Ai,1在一个环(连通分量)中。
所以,用tarjan可以判断是否有解。一个2-sat问题有解,当且仅当任意一个Ai都有Ai,0和Ai,1不在一个环(连通分量)中。

方案

如果要求出具体方案呢?
从一个子问题开始,如果一个点没有出度,说明它选择之后没有什么其它情况需要考虑,此时的最佳方案就是选择这个点。
也就是说,我们要依次遍历出度为0的节点。实现时,建个反图,再跑拓扑排序就可以了。
对于Ai,我们考虑选择Ai,0和Ai,1中拓扑序较小的。具体实现时,给每个Ai一个标记,第一次访问Ai,p时,给Ai,p标记0,Ai,1-p标记1。不要想太复杂,这其实就是选Ai,p的意思,一定要模拟一下(假设p=0或1),很容易明白。
想要优化这个算法,关键还是要追回程序本身来看。细心的人可以发现,tarjan算法本身就是以自底向上的拓扑序来遍历所有连通块的。所以我们可以省去建反图和拓扑排序,直接利用tarjan的结果。
一句话搞定:choose[i]=bel[i] 这本身的原理还是选择一个拓扑序小的节点作为Ai的答案。

例题

poj3683 Priest John's Busiest Day

题解
O(N^2)判断两两间的仪式时间是否冲突,若冲突则存在必选问题。
记i场婚礼的两场仪式分别为id(i,0),id(i,1)。
如果id(i,p)和id(j,q)发生冲突,那么连边( id(i,p) , id(j,1-q) )和( id(j,q) , (i,1-p) )。

代码

#include
#include
#include
#define id(i,j) i+(j)*n//debug i+j*n
using namespace std;
const int maxn=1010;
const int di[4]={0,0,1,1},dj[4]={0,1,0,1};

int n;
int ke[maxn];
int begin[maxn][2];

struct E{int y,next;}e[maxn*maxn*8];int len=1,last[maxn*2];
void ins(int x,int y)
{
    e[++len]=(E){y,last[x]};last[x]=len;
}

/*bool chong(int i,int p,int j,int q)不能这样写,要考虑边界问题 
{
    if(begin[i][p] <= begin[j][q] && begin[j][q] <= begin[i][p]+ke[i]) return true;
//    else false;debug
    return false;
}*/
bool chong(int a,int b,int c,int d)
{
    if(c<=a&&a

 

你可能感兴趣的:(2-sat)