POJ 3683 Priest John's Busiest Day (2-SAT 输出路径)

题目大意:

一个牧师要去主持婚礼,每对新人夫妻会给你两个时间和一个持续时间,分别为 from to 和value
你可以在from到from+value的时间段里给她们们举行婚礼,也可以在to-value到to的时间段里为她们举行婚礼,但是总是要选一个的。
然后现在给你n对夫妻的。问你能不能有一种可能的方案,如果有,按照输入的顺序,将每对夫妻举行婚礼的时间段输出出来。
第i对夫妻的两个时间段可以看成i和i+n这两个点,两个点中必选一个,典型的2-SAT问题。
关键是找到一组可行解。
总之大体思路即:通过矛盾关系建边,比如i和j有矛盾关系,也就是说这两个点不能同时选。所以建立i到j+n的边和j到i+n的边。然后tarjan跑强连通分量,对于每个点i,判断他和他所对应的点i+n是否在同一个联通分量里,如果在的话,说明存在矛盾,无解。否则,反向建图,拓扑排序,从入读为0的点开始选,如果没选过,那么直接选,然后对应着的新点肯定不选,然后从这个点把不选关系传递下去。

注意:

我们必须明确几个问题:
1. 由于2-SAT题目的特殊性,我们在建边的时候,总是建的对称的边,最后也肯定是一个对称的图,所以即使在经过缩点后,图依旧是对称的。
2. 缩点后正向建图也可以,只不过这时需要找出度为0的点,也不是不可以,只是你会发现你不容易删去跟此点有关的边,所以就很复杂。
3. 注意在构建新图的时候一定要注意重边,建出重边的话拓扑排序就会错。。

ac代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
#include <queue>
using namespace std;
const int maxn = 2200;
struct edge{
    int from,to,v,next;
}ed[40000000],a[2200];
int head[maxn];
int cnte,cnte2;
void ae(int x,int y){
    ed[++cnte].to = y;
    ed[cnte].next = head[x];
    head[x]=cnte;
}

int ma[2200][2200];// 缩点之后的新图(反向图)
int col[2200];//染成的颜色 1代表一个集合 -1代表i+n的那集合 0代表未访问过
int fanxiang[2200];
int dfn[maxn],low[maxn],vis[maxn],stak[maxn],belong[maxn],cntc,cnts,index;//strong connected component //cnt of stack
void dfs(int u){ //tarjan里的dfs
    dfn[u]=low[u] = ++index;
    stak[cnts++]=u;
    vis[u]=1;
    for(int i = head[u];i!=-1;i=ed[i].next){
        int v = ed[i].to;
        if(!dfn[v]){
            dfs(v);
            low[u] = min(low[u],low[v]);
        }
        else if(vis[v]){
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        cntc++;int v;
        do{
            v = stak[--cnts];
            vis[v] = 0;
            belong[v] = cntc;
            fanxiang[cntc] = v;
        }while(v!=u);
    }
}
int n,m;
void tarjan(){
    for(int i = 1;i <= 2*n;i++){
        if(!dfn[i]){
            dfs(i);
        }
    }
}
void f(int i ,int j){// 建边 (构建必选关系)
    int len1 = a[i].v,len2 = a[j].v;
    if( !(a[i].from+len1 <= a[j].from || a[j].from+len2 <= a[i].from) ){
        ae(i,j+n);ae(j,i+n);
    }
    if( !(a[i].from+len1 <= a[j].to-len2 || a[j].to <= a[i].from) ){
        ae(i,j);ae(j+n,i+n);
    }
    if( !(a[i].to <= a[j].from || a[j].from+len2 <= a[i].to-len1) ){
        ae(i+n,j+n);ae(j,i);
    }
    if( !(a[i].to <= a[j].to-len2 || a[j].to<= a[i].to-len1) ){
        ae(i+n,j);ae(j+n,i);
    }
}
int in[maxn];
void dfscol(int u){//将不可选关系传递下去,
    for(int i = 1;i <= cntc;i++){
        if(ma[u][i] == 1 && !col[i]){
            col[i] = -1;
            dfscol(i);
        }
    }
}
int main(){
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    memset(ma,0,sizeof(ma));
    int h1,m1,h2,m2,value;
    for(int i = 1;i <= n;i++){
        scanf("%d:%d %d:%d %d",&h1,&m1,&h2,&m2,&value);
        a[i].from = h1*60+m1;
        a[i].to = h2*60+m2;
        a[i].v = value;
    }
    for(int i = 1;i <= n;i++){
        for(int j = i+1;j <= n;j++) f(i,j);
    }
    tarjan();
    int flag = 1;
    for(int i = 1;i <= n;i++){
        if(belong[i] == belong[i+n]){
            flag = 0;break;
        }
    }
    if(flag == 0){
        puts("NO");
    }
    else{
        puts("YES");
        queue<int> q;
        for(int i = 1;i <= 2*n;i++){//反向建新图 同时进行入度统计
            for(int j = head[i];j!=-1;j=ed[j].next){
                int to = ed[j].to,ii = belong[i],jj = belong[to];
                if(ii != jj && ma[jj][ii]==0){//判重边
                    ma[jj][ii] = 1;in[ii]++;
                }
            }
        }
        for(int i = 1;i <= cntc;i++){//拓扑排序 取入读为0的点
            if(in[i] == 0) q.push(i);
        }
        int top;
        while(!q.empty()){//依次选择当前没有被选过的点 染上色1 并将他对立的新点染成-1
            top = q.front();
            q.pop();
            for(int i = 1;i <= cntc;i++){
                if(ma[top][i] == 1){
                    in[i]--;
                    if(in[i] == 0)
                        q.push(i);
                }
            }
            if(col[top] == 0){
                col[top] = 1;
                int to = fanxiang[top]; // 为了找到对立的新点 通过对立的原点来找
                if(to > n) to -= n;
                else to += n;
                to = belong[to];col[to] = -1;
                dfscol(to);
            }
        }
        for(int i = 1;i <= n;i++){ // 对每个点(边) 判断他所处的新点的颜色,然后输出
            if(col[belong[i]] == 1){
                h1 = a[i].from/60;m1 = a[i].from % 60;
                printf("%02d:%02d ",h1,m1);
                a[i].from += a[i].v;
                h1 = a[i].from/60;m1 = a[i].from % 60;
                printf("%02d:%02d\n",h1,m1);
            }
            else{
                a[i].to -= a[i].v;
                h1 = a[i].to/60;m1 = a[i].to % 60;
                printf("%02d:%02d ",h1,m1);
                a[i].to += a[i].v;
                h1 = a[i].to/60;m1 = a[i].to % 60;
                printf("%02d:%02d\n",h1,m1);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值