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

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/82959097

介绍

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]<bel[i+n],i,i+n分别为Ai,0和Ai,1两个决策。
这本身的原理还是选择一个拓扑序小的节点作为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<cstdio>
#include<cstring>
#include<algorithm>
#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<d || c<b&&b<=d || a<=c&&d<=b) return true;
    return false;
}

int id,dfn[maxn*2],low[maxn*2];
int top,sta[maxn*2];bool v[maxn*2];
int scc,bel[maxn*2];
void tarjan(int x)
{
    dfn[x]=low[x]=++id;
    sta[++top]=x;v[x]=true;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(v[y]) low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x])
    {
        scc++;int i;
        do
        {
            i=sta[top--];
            v[i]=false;
            bel[i]=scc;
        }while(i!=x);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int h[2],m[2];
        scanf("%d:%d %d:%d %d",&h[0],&m[0],&h[1],&m[1],&ke[i]);
        begin[i][0]=h[0]*60+m[0];//debug h[i][0]*60+h[i][0]
        begin[i][1]=h[1]*60+m[1]-ke[i];//debug 在h[i][1]:m[i][1]时结束
    }
    for(int i=1;i<n;i++)
        for(int j=i+1;j<=n;j++)
            for(int k=0;k<4;k++)
                if(chong(begin[i][di[k]],begin[i][di[k]]+ke[i],begin[j][dj[k]],begin[j][dj[k]]+ke[j]))
                {
                    ins(id(i,di[k]),id(j,1-dj[k]));
                    ins(id(j,dj[k]),id(i,1-di[k]));
                }
    
    for(int i=1;i<=2*n;i++)
        if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
        if(bel[i]==bel[i+n]){puts("NO");return 0;}
    puts("YES");
    for(int i=1;i<=n;i++)
        if(bel[i]<bel[i+n])
            printf("%02d:%02d %02d:%02d\n",
            begin[i][0]/60,begin[i][0]%60,
            (begin[i][0]+ke[i])/60,(begin[i][0]+ke[i])%60);
        else
            printf("%02d:%02d %02d:%02d\n",
            begin[i][1]/60,begin[i][1]%60,
            (begin[i][1]+ke[i])/60,(begin[i][1]+ke[i])%60);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/82959097