POJ_3683 Priest John's Busiest Day 2-Sat

http://poj.org/problem?id=3683

题意:有N场婚礼,每场婚礼有一个开始时间和结束时间,并且每场婚礼都会举行一个仪式,这个仪式需要有一个司仪,并且这个仪式要么就在婚礼的一开始就举行,要么就在婚礼快结束的时候举行。但是现在只有一个司仪,司仪不能在同一时间参加两个不同婚礼的仪式,问是否存在这样的安排,使得司仪参见这些仪式不冲突,若不存在则输出“NO” , 若存在,则输出一种安排。

思路:2-Sat问题,这是一个典型的2-Sat问题,建图的顶点是每次婚礼的仪式的时间,对于第i场婚礼,两个时间分别用点i和i+N表示,冲突的情况分析如下,假设i举行仪式的时间是:[ a ,b ] , j举行仪式的时间是:[ c, d ] ,则两者不冲突的条件是:b <= c || d <= a (边界冲突不算冲突)。建图之后强两通缩点,缩点完了之后进行判断,判断是否有同一场婚礼的不同的仪式时间在同一个强连通分量里面,最后求一次拓扑排序求出任意一种可行的方案。

代码:

#include<stdio.h>
#include<string.h>
const int MAXN = 2010 ;
int N ;
int s[MAXN] , e[MAXN] ;
int Gnext[MAXN*MAXN],Gv[MAXN*MAXN],Gr[MAXN],Gc ;
int dfn[MAXN] , low[MAXN] , stack[MAXN] , belong[MAXN] ,cf[MAXN];
bool in[MAXN] ;
int top ,idx ,Bcnt ;

int gnext[MAXN*MAXN],gv[MAXN*MAXN],gr[MAXN],gc ;
int degree[MAXN],que[MAXN] ,front ,rear ,col[MAXN];

bool is_inter(int i , int j){
    int a ,b,c,d;
    a = s[i] ; b = e[i] ; c=s[j] ; d=e[j] ;
    if( b<=c || d<=a )  return false ;
    return true ;
}
void add(int a, int b){
    Gv[Gc] = b ;
    Gnext[Gc] = Gr[a] ;
    Gr[a] = Gc++ ;
}
void build(){
    memset(Gr,-1,sizeof(Gr)) ; Gc = 0 ;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=N;j++){
            if(i == j)  continue ;
            if( is_inter(i,j) )   add(i,j+N);		//建图
            if( is_inter(i,j+N) ) add(i,j) ;
            if( is_inter(i+N,j) )   add(i+N,j+N);
            if( is_inter(i+N,j+N) ) add(i+N,j) ;
        }

    }
}
void tarjin(int u){
    int v ;
    low[u] = dfn[u] = ++idx ;
    stack[++top] = u ; in[u] = 1 ;
    for(int i=Gr[u] ;i!=-1;i=Gnext[i]){
        v = Gv[i] ;
        if( !dfn[v] ){
            tarjin(v) ; if(low[v] < low[u]) low[u] = low[v] ;
        }
        else if( in[v] && dfn[v]<low[u])
            low[u] = dfn[v] ;
    }
    if( low[u] == dfn[u] ){
        Bcnt++ ;
        do{
            v = stack[top--] ; in[v] = 0 ;
            belong[v] = Bcnt ;
        }while(v != u) ;
    }
}
void add2(int a, int b){
    gv[gc] = b ;
    gnext[gc] = gr[a] ;
    gr[a] = gc++ ;
}
void solve(){
    top = idx = Bcnt = 0 ;
    memset(dfn , 0 ,sizeof(dfn)) ;
    memset(in , 0 ,sizeof(in) ) ;
    for(int i=1;i<=2*N;i++){
        if( !dfn[i] )   tarjin(i);
    }
    bool ok = 1 ;
    for(int i=1;i<=N;i++){
        if( belong[i] == belong[i+N] ){
            ok = 0 ; break ;
        }
        /*
        为下面的拓扑排序作准备。
        */
        cf[ belong[i] ] = belong[i+N] ;
        cf[ belong[i+N] ] = belong[i] ;
    }
    if( !ok ){
        printf("NO\n"); return ;
    }
    printf("YES\n");
    memset( gr,-1,sizeof(gr)); gc = 0 ;
    memset(degree, 0 ,sizeof(degree));
    for(int i=1;i<=2*N;i++){        //缩点,建新图
        for(int j=Gr[i] ;j!=-1;j=Gnext[j]){
            int v = Gv[j] ;
            int a = belong[i] ;
            int b = belong[v] ;
            if(a != b){
                add2(b,a);
                degree[a] ++ ;
            }
        }
    }
    front = rear = 0 ;
    for(int i=1;i<=Bcnt;i++){
        if( degree[i] == 0 )    que[rear++] = i ;
    }
    memset(col, 0,sizeof(col)) ;
    while(front != rear){
        int u = que[front++]  ;
        if( col[u] == 0 ){
            col[u] = 1 ;
            col[ cf[u] ] = -1 ;
        }
        for(int i=gr[u];i!=-1;i=gnext[i]){
            int v = gv[i] ;
            if( --degree[v] == 0 )  que[rear++]  =v ;
        }
    }
    int a, b, c ,d ;
    for(int i=1;i<=N;i++){
        if( col[ belong[i] ] == 1){
            a = s[i] / 60 ;
            b = s[i] % 60 ;
            c = e[i] / 60 ;
            d = e[i] % 60 ;
            printf("%02d:%02d %02d:%02d\n",a,b,c,d);
        }
        else{
            a = s[i+N] / 60 ;
            b = s[i+N] % 60 ;
            c = e[i+N] / 60 ;
            d = e[i+N] % 60 ;
            printf("%02d:%02d %02d:%02d\n",a,b,c,d);
        }
    }
}
int main(){
    int a,b, c ,d ,ee;
    while(scanf("%d",&N) == 1){
        for(int i=1;i<=N;i++){
            scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&ee);
            a = a*60+b ;
            c = c*60+d ;
            s[i]=a ; e[i]=a+ee ;
            s[i+N]=c-ee ; e[i+N]=c ;
        }
        build() ;
        solve() ;
    }
    return 0 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值