poj3683 Priest John's Busiest Day(2_SAT【方案)

本文探讨了一种基于2SAT问题解决N对夫妇在特定时间段内举行婚礼,并确保John能够参加每个婚礼的特殊仪式的方法。通过构建图模型,使用Tarjan算法进行缩点判断,并采用拓扑排序染色法来构造可行解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

题意:
N对夫妇计划在同一天结婚, 第i对夫妇计划在Si到Ti之间举行婚礼,第i对夫妇需要Di分钟来完成一个特殊的仪式,此外,这个仪式必须在婚礼的开始或结束时(即它必须从Si到Si + Di,或从Ti-Di到Ti)
问如何安排可以使John在每一个婚礼特别仪式上出席

分析:
我们需要在每一场婚礼的开始或结束中选择一段举行仪式,使得n场婚礼没有冲突
显然是一个2 SAT问题,那我们要如何建图呢?

实际上这道题和poj3207有异曲同工之妙

我们无脑暴力判断两个婚礼的仪式时间是否存在冲突
(a1,a2),(b1,b2) ( a 1 , a 2 ) , ( b 1 , b 2 )
如果 a1,b1 a 1 , b 1 存在矛盾: a1>b2,b1>a2 a 1 − > b 2 , b 1 − > a 2
如果 a1,b2 a 1 , b 2 存在矛盾: a1>b1,b2>a2 a 1 − > b 1 , b 2 − > a 2
如果 a2,b1 a 2 , b 1 存在矛盾: a2>b2,b1>a1 a 2 − > b 2 , b 1 − > a 1
如果 a2,b2 a 2 , b 2 存在矛盾: a2>b1,b2>a1 a 2 − > b 1 , b 2 − > a 1
这样无脑连一tong之后,有解的必然还是有解,无解的情况一定包含在内了
(也就是说,保证正确性)

tarjan缩点判断可行性

如果可行,我们就面临着一个新的问题:构造可行解

dalao说这种方法叫:拓扑排序染色法,实际上就是模拟一遍2 SAT的过程
我们建出新图(反向连边)进行top排序
遇到一个没有标记的结点,就标记为选择
之后把ta的对立结点设置为不可选择,沿着边传递不可选择的标记即可

tip

一开始我把小时和分钟分开考虑,非常 * *
实际上我们统一用分钟计时就好了

#include<cstring>
#include<cstdio>
#include<iostream>

using namespace std;

const int N=2005;
int n,st[N],tot=0,ste[N],tet=0,du[N],opp[N],col[N];
struct node{
    int y,nxt;
};
node way[4000000],e[4000000];
struct point{
    int s,t;
};
point p[N];

void add(int u,int w) {
    tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}

void _add(int u,int w) {
    tet++;e[tet].y=w;e[tet].nxt=ste[u];ste[u]=tet;
    du[w]++;
}

bool jiao(int a,int b) {
    if (p[a].s>=p[b].t||p[a].t<=p[b].s) return 0;
    return 1;
}

int low[N],dfn[N],clo=0,S[N],top=0,cnt=0,belong[N];
bool vis[N];

void tarjan(int now) {
    S[++top]=now;
    low[now]=dfn[now]=++clo;
    vis[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (!dfn[way[i].y]) {
            tarjan(way[i].y);
            low[now]=min(low[now],low[way[i].y]);
        }
        else if (vis[way[i].y])
            low[now]=min(low[now],dfn[way[i].y]);
    if (low[now]==dfn[now]) {
        cnt++;
        int x=0;
        while (x!=now) {
            x=S[top--];
            belong[x]=cnt;
            vis[x]=0;
        }
    }       
}

bool solve() {
    for (int i=1;i<=n*2;i++)
        if (!dfn[i])
            tarjan(i);
    for (int i=1;i<=n;i++)
        if (belong[i]==belong[i+n]) return 0;
    return 1;
}

void rebuild() {
    for (int i=1;i<=2*n;i++)
        for (int j=st[i];j;j=way[j].nxt)
            if (belong[i]!=belong[way[j].y])
                _add(belong[way[j].y],belong[i]);   //反向连边 
}

void dfs(int x) {
    if (col[x]) return;
    col[x]=-1;               //标记为不可选 
    for (int i=ste[x];i;i=e[i].nxt)
        dfs(e[i].y);
}

void TOP() {
    top=0;
    for (int i=1;i<=cnt;i++)
        if (!du[i]) S[++top]=i;
    while (top) {
        int now=S[top--];
        if (col[now]) continue;
        col[now]=1;
        dfs(opp[now]);
        for (int i=ste[now];i;i=e[i].nxt)
        {
            du[e[i].y]--;
            if (!du[e[i].y]) S[++top]=e[i].y;
        }
    }
}

void print(int x) {
    printf("%.2d:%.2d %.2d:%.2d\n",p[x].s/60,p[x].s%60,p[x].t/60,p[x].t%60);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        int a,b,c,d,t;
        scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&t);
        p[i].s=a*60+b; p[i].t=p[i].s+t;            //i,i+n属于一个集合 
        p[i+n].t=c*60+d; p[i+n].s=p[i+n].t-t;
    }

    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++) {
            if (jiao(i,j)) add(i,j+n),add(j,i+n);
            if (jiao(i,j+n)) add(i,j),add(j+n,i+n);
            if (jiao(i+n,j)) add(i+n,j+n),add(j,i);
            if (jiao(i+n,j+n)) add(i+n,j),add(j+n,i);
        }

    if (!solve()) {
        printf("NO\n");
        return 0;
    } 
    else printf("YES\n");

    rebuild();
    for (int i=1;i<=n;i++) {         //对立集合 
        opp[belong[i]]=belong[i+n];
        opp[belong[i+n]]=belong[i];
    }
    TOP();
    for (int i=1;i<=n;i++)
        if (col[belong[i]]==1)       //已选
            print(i);
        else print(i+n);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值