题目大意:
一个牧师要去主持婚礼,每对新人夫妻会给你两个时间和一个持续时间,分别为 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;
}