2-SAT

54 篇文章 0 订阅

解释

对于布尔方程(aVbVc…….)^(dVeVf……)^………,我们称(aVbVc…….),(dVeVf……)为子句,a,b,c,d,e,f…..为文字,若每个子句的文字均不超过两个,那么求解这样的方程被称为2-SAT.
此算法可以利用强连通分量高效求解这类方程.

实现方法

若aVb=1,我们可以发现当!a时,b必须为1,当!b时,a必须为1,也就是说,若!a为真,则b为真,若!b为真,则a为真.
如果方程内有n个文字,则我们可以建2*n个点,i和i+n两点分别表示i=1和i=0两种状态,因此两种状态必然一真一假,若a状态可以推出b状态,则a向b连一条有向边.
可以发现,对于这张图上的强连通分量,每个点全为真或全为假,若a和!a出现在同一个强连通分量中则说明此方程无解,反之有解.

输出方案

对这张图进行拓扑排序(因而建议用Kosaraju求强连通分量),若x所在的强连通分量的拓扑序在!x所在的强连通分量之后,则x为真.

例题 poj 3683

题意

有n件事情要做,每件事情可以在两个时间段中选一个时间段做,问是否可以将n将事情做完.

做法

首先如果在第一个时间段做则为真,反之为假,可以根据时间段间的矛盾来建边,若a,b两时间段有交集,则a向!b,b向!a连边,最后求强连通分量即可.

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2010
#define M 4001000
using namespace std;

int n,s[2][N],t[N],bb,first[N],num[N],tim[N],tt,lt,aa,a[M],b[M];
bool vis[N],ans[N];
struct Bn
{
    int to,next;
}bn[M];

inline void add(int u,int v)
{
    a[++aa]=u;
    b[aa]=v;
    bb++;
    bn[bb].to=v;
    bn[bb].next=first[u];
    first[u]=bb;
}

inline void ad(int u,int v)
{
    bb++;
    bn[bb].to=v;
    bn[bb].next=first[u];
    first[u]=bb;
}

void dfs(int now)
{
    int p,q;
    vis[now]=1;
    for(p=first[now];p!=-1;p=bn[p].next)
    {
        if(!vis[bn[p].to]) dfs(bn[p].to);
    }
    tim[++tt]=now;
}

void Dfs(int now)
{
    num[now]=lt;
    vis[now]=1;
    int p,q;
    for(p=first[now];p!=-1;p=bn[p].next)
    {
        if(!vis[bn[p].to]) Dfs(bn[p].to);
    }
}

inline bool scc()
{
    int i;
    for(i=1;i<=n;i++)
    {
        if(!vis[i]) dfs(i);
    }
    memset(first,-1,sizeof(first));
    bb=0;
    for(i=1;i<=aa;i++)
    {
        ad(b[i],a[i]);
    }
    memset(vis,0,sizeof(vis));
    for(i=tt;i>=1;i--)
    {
        if(!vis[tim[i]])
        {
            lt++;
            Dfs(tim[i]);
        }
    }
    for(i=1;i<=n;i++)
    {
        if(num[i]==num[i+n]) return 0;
    }
    return 1;
}

int main()
{
    memset(first,-1,sizeof(first));
    int i,j,a,b,c,d;
    cin>>n;
    for(i=1;i<=n;i++)
    {
        scanf("%d:%d %d:%d%d",&a,&b,&c,&d,&t[i]);
        s[0][i]=a*60+b;
        s[1][i]=c*60+d-t[i];
    }
    for(i=1;i<=n;i++)
    {
        for(j=i+1;j<=n;j++)
        {
            if(min(s[0][i]+t[i],s[0][j]+t[j])>max(s[0][i],s[0][j]))
            {
                add(i,j+n);
                add(j,i+n);
            }
            if(min(s[1][i]+t[i],s[0][j]+t[j])>max(s[1][i],s[0][j]))
            {
                add(i+n,j+n);
                add(j,i);
            }
            if(min(s[0][i]+t[i],s[1][j]+t[j])>max(s[0][i],s[1][j]))
            {
                add(i,j);
                add(j+n,i+n);
            }
            if(min(s[1][i]+t[i],s[1][j]+t[j])>max(s[1][i],s[1][j]))
            {
                add(i+n,j);
                add(j+n,i);
            }
        }
    }
    if(!scc())
    {
        puts("NO");
        return 0;
    }
    puts("YES");
    for(i=1;i<=n;i++)
    {
        if(num[i]>num[i+n]) ans[i]=1;
    }
    for(i=1;i<=n;i++)
    {
        ans[i]?printf("%02d:%02d %02d:%02d\n",s[0][i]/60,s[0][i]%60,(s[0][i]+t[i])/60,(s[0][i]+t[i])%60):printf("%02d:%02d %02d:%02d\n",s[1][i]/60,s[1][i]%60,(s[1][i]+t[i])/60,(s[1][i]+t[i])%60);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值