【POJ】3683 Priest John's Busiest Day 2-sat

本文介绍了一种使用2-SAT算法解决婚礼祝福时间调度问题的方法。通过建立冲突条件下的二部图,并利用拓扑排序来找出可行解,确保牧师能够在不冲突的时间段内为多场婚礼提供祝福。

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

传送门:Priest John's Busiest Day


题目分析:2-sat并求可行解。

一场婚礼可以在两个时间段中挑一个接受牧师的祝福,所以可以将这两个时间段作为一组。

如果一场婚礼的某个时间段与另一场婚礼的某个时间段冲突,则建边。

最后如果有可行解,则缩点后建反向图,跑一遍拓扑排序,每次从队列中取出的未染色的块染为红色,将对立块(~i所在的块)染为蓝色。最后所有染为红色的块中的元素即所求的可行解。


代码如下:


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define FOR( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define REV( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define CLR( a , x ) memset ( a , x , sizeof a )
#define CPY( a , x ) memcpy ( a , x , sizeof a )

const int MAXN = 2000 ;
const int MAXE = 2000000 ;

struct Edge {
	int v ;
	Edge* next ;
} E[MAXE] , *H[MAXN] , *cur , t_E[MAXE] , *t_H[MAXN] , *t_cur ;

struct Point {
	int st , ed ;
} p[MAXN] ;

int dfn[MAXN] , low[MAXN] , scc[MAXN] , scc_cnt ;
int S[MAXN] , top , dfs_clock ;
int n , m ;
int in[MAXN] ;
int Q[MAXN] , head , tail ;
int color[MAXN] ;
int opp[MAXN] ;

void init () {
	cur = E ;
	top = scc_cnt = dfs_clock = 0 ;
	CLR ( H , 0 ) ;
	CLR ( dfn , 0 ) ;
	CLR ( scc , 0 ) ;
}

void t_init () {
	t_cur = t_E ;
	CLR ( t_H , 0 ) ;
	CLR ( in , 0 ) ;
}

void addedge ( int u , int v ) {
	cur -> v = v ;
	cur -> next = H[u] ;
	H[u] = cur ++ ;
}

void t_addedge ( int u , int v ) {
	t_cur -> v = v ;
	t_cur -> next = t_H[u] ;
	t_H[u] = t_cur ++ ;
}

void tarjan ( int u ) {
	dfn[u] = low[u] = ++ dfs_clock ;
	S[top ++] = u ;
	for ( Edge* e = H[u] ; e ; e = e -> next ) {
		int v = e -> v ;
		if ( !dfn[v] ) {
			tarjan ( v ) ;
			low[u] = min ( low[u] , low[v] ) ;
		} else if ( !scc[v] ) low[u] = min ( low[u] , dfn[v] ) ;
	}
	if ( low[u] == dfn[u] ) {
		++ scc_cnt ;
		do {
			scc[S[-- top]] = scc_cnt ;
		} while ( u != S[top] ) ;
	}
}

int ok () {
	REP ( i , 0 , n << 1 ) if ( !dfn[i] ) tarjan ( i ) ;
	REP ( i , 0 , n ) if ( scc[i << 1] == scc[i << 1 | 1] ) return 0 ;
	REP ( i , 0 , n ) {
		opp[scc[i << 1]] = scc[i << 1 | 1] ;
		opp[scc[i << 1 | 1]] = scc[i << 1] ;
	}
	return 1 ;
}

int check ( int i , int j ) {
	return p[i].st < p[j].ed && p[j].st < p[i].ed ;
}

void topo () {
	head = tail = 0 ;
	CLR ( color , 0 ) ;
	FOR ( i , 1 , scc_cnt ) if ( !in[i] ) Q[tail ++] = i ;
	while ( head != tail ) {
		int u = Q[head ++] ;
		if ( !color[u] ) {
			color[u] = 1 ;
			color[opp[u]] = 2 ;
			for ( Edge* e = t_H[u] ; e ; e = e -> next )
				if ( -- in[e -> v] == 0 )
					Q[tail ++] = e -> v ;
		}
	}
}

void solve () {
	int h1 , h2 , m1 , m2 , d ;
	init () ;
	REP ( i , 0 , n ) {
		scanf ( "%d:%d %d:%d %d" , &h1 , &m1 , &h2 , &m2 , &d ) ;
		p[i << 1].st = h1 * 60 + m1 ;
		p[i << 1].ed = h1 * 60 + m1 + d ;
		p[i << 1 | 1].st = h2 * 60 + m2 - d ;
		p[i << 1 | 1].ed = h2 * 60 + m2 ;
	}
	REP ( i , 0 , n ) REP ( j , i + 1 , n ) {
		int Li = i << 1 ;
		int Lj = j << 1 ;
		int Ri = i << 1 | 1 ;
		int Rj = j << 1 | 1 ;
		if ( check ( Li , Lj ) ) addedge ( Li , Rj ) , addedge ( Lj , Ri ) ;
		if ( check ( Li , Rj ) ) addedge ( Li , Lj ) , addedge ( Rj , Ri ) ;
		if ( check ( Ri , Lj ) ) addedge ( Ri , Rj ) , addedge ( Lj , Li ) ;
		if ( check ( Ri , Rj ) ) addedge ( Ri , Lj ) , addedge ( Rj , Li ) ;
	}
	if ( ok () ) {
		printf ( "YES\n" ) ;
		t_init () ;
		REP ( i , 0 , n << 1 )
			for ( Edge* e = t_H[i] ; e ; e = e -> next )
				if ( scc[i] != scc[e -> v] ) {
					t_addedge ( scc[e -> v] , scc[i] ) ;
					++ in[scc[i]] ;
				}
		topo () ;
		REP ( i , 0 , n << 1 )
			if ( color[scc[i]] == 1 )
				printf ( "%02d:%02d %02d:%02d\n" , p[i].st / 60 , p[i].st % 60 , p[i].ed / 60 , p[i].ed % 60 ) ;
	} else printf ( "NO\n" ) ;
}
	
int main () {
	while ( ~scanf ( "%d" , &n ) ) solve () ;
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值