POJ 3683 2-sat

本文介绍了一种使用2-SAT算法解决婚礼仪式调度问题的方法,确保单一神父能在不同时段为多场婚礼进行特别仪式,避免时间冲突。通过建立合适的图模型并运行2-SAT算法来验证解决方案的可行性。

题目链接:http://poj.org/problem?id=3683
题意:每个婚礼有两个时段(婚礼开始,或者结束)可以进行特别仪式,特别仪式必须要有神父在场,神父只有一个,问是否能满足所有婚礼的需求。
规模:( 1 ≤ N ≤ 1000),N场婚礼
类型: 2-sat
分析:这是“大白书”上的2-sat基础。
每场婚礼都只有两个时间段,而且不能冲突,是2-sat的典型模型。
我们将所有不能共存的关系列出,据此建边,跑2-sat就行了。
这里稍微解释一下建边过程:

对于两场婚礼a,b
用a表示“婚礼开始时举行仪式”,用(!a)表示“婚礼结束时举行仪式”,b同a;
那么我们有四种组合方式:
(a,b),(a,!b),(!a,b),(!a,!b)
对于每组确定的a,b,我们可以得到这四种组合是否成立。
因为求的是2-sat,我们只需要对冲突的组合建边:

这里写图片描述

时间复杂度&&优化:
代码:

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>

using namespace std;

const int MAXN=2005;
const int MAXM=40000000;

int n;

struct Time{
    int s,t;
    int l;
}time[MAXN];

struct Edge{
    int to,next;
}edge[MAXM];
int head[MAXN],tot;

void init(){
    tot=0;
    memset(head,-1,sizeof(head));
}

void add_edge(int u,int v){
    edge[tot].to=v;edge[tot].next=head[u];head[u]=tot++;
}

void ADD(int i,int j){
    Time a=time[i];
    Time b=time[j];
    if(min(a.s+a.l,b.s+b.l)>max(a.s,b.s)){
        //a=>!b,b=>!a
        add_edge((2*i),(2*j)^1);
        add_edge((2*j),(2*i)^1);
    }
    if(min(a.s+a.l,b.t)>max(a.s,b.t-b.l)){//cout<<2;
        add_edge((2*i),2*j);
        add_edge((2*j)^1,(2*i)^1);
    }
    if(min(a.t,b.s+b.l)>max(a.t-a.l,b.s)){//cout<<3;
        add_edge((2*i)^1,(2*j)^1);
        add_edge(2*j,2*i);
    }
    if(min(a.t,b.t)>max(a.t-a.l,b.t-b.l)){//cout<<4;
        add_edge((2*i)^1,2*j);
        add_edge((2*j)^1,2*i);
    }
}


bool vis[MAXN];
int S[MAXN],top;

bool dfs(int u){
    if(vis[u^1])return false;
    if(vis[u])return true;
    vis[u]=true;
    S[top++]=u;
    for(int i=head[u];i!=-1;i=edge[i].next){
        if(!dfs(edge[i].to))
            return false;
    }
    return true;
}

bool Twosat(int n){
    memset(vis,false,sizeof(vis));
    for(int i=0;i<n;i+=2){
        if(vis[i]||vis[i^1])continue;
        top=0;
        if(!dfs(i)){
            while(top)vis[S[--top]]=false;
            if(!dfs(i^1))return false;
        }
    }
    return true;
}

void solve(){
    if(Twosat(2*n)){
        printf("YES\n");
        for(int i = 0;i < n;i++)
            if(vis[i*2]){
                printf("%02d:%02d %02d:%02d\n",time[i].s/60,time[i].s%60,(time[i].s+time[i].l)/60,(time[i].s+time[i].l)%60);
            }
            else{
                printf("%02d:%02d %02d:%02d\n",(time[i].t-time[i].l)/60,(time[i].t-time[i].l)%60,time[i].t/60,time[i].t%60);
            }
        return;
    }
    printf("NO\n");
}

int main()
{
    while(scanf("%d",&n)==1){
        init();
        for(int i=0;i<n;i++){
            int a,b,c;
            scanf("%d:%d",&a,&b);
            time[i].s=a*60+b;
            scanf("%d:%d",&a,&b);
            time[i].t=a*60+b;
            scanf("%d",&time[i].l);
        }

        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                ADD(i,j);
            }
        }
//        for(int i=0;i<2*n;i++){
//            cout<<i<<endl;
//            for(int j=head[i];j!=-1;j=edge[j].next)
//                printf("%d ",edge[j].to);
//            cout<<endl;
//        }
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值