poj3683 Priest John's Busiest Day
题意:一个小镇里面只有一个牧师,现在有些新人要结婚,需要牧师分别去主持一个仪式,给出每对新人婚礼的开始时间 s 和结束时间 t ,还有他们俩的这个仪式需要的时间(每对新人需要的时间长短可能不同) d ,牧师可以在婚礼开始的时间 d 内(s 到 s+d)或者是结束前的时间 d 内(t - d 到 t)完成这个仪式。现在问能否给出一种安排,让牧师能完成所有夫妇婚礼的仪式,如果可以,输出一种安排。
我们把每个婚礼可能的两个时间段看做两个点 A 和 A’,显然,如果两个时间段冲突(比如 A 和 B 的时间重合),那么需要建边(A -> B' ),(B -> A‘),判断出是否存在解后,需要输出一组解,这里的方法是 赵爽 的《2-SAT 解法浅析》里面看的,有详细的证明。
具体操作就是:求强联通,缩点重新建图(这里建反向图,参见论文),然后给图中的点着色,将一个未着色点 x 上色同时,把与它矛盾的点 y 以及 y 的所有子孙节点上另外一种颜色,上色完成后,进行拓扑排序,选择一种颜色的点输出就是一组可行解。
代码:
#include<cstdio> #include<cstring> #include<stack> #include<queue> #include<climits> using namespace std; const int N = 2010; struct point{ int st,et; }p[N]; struct Edge{ int s,e,next; }edge[N*N],edge_new[N*N]; int n,m,e_num,vis_num,cnt,head[N],instack[N],low[N],tim[N],belong[N]; int e_new,sp_num,sp[2][N]; int indeg[2*N],cf[2*N],col[2*N]; void AddEdge(int a,int b){ edge[e_num].s=a; edge[e_num].e=b; edge[e_num].next=head[a]; head[a]=e_num++; } void AddEdge_new(int a,int b){ edge_new[e_new].s=a; edge_new[e_new].e=b; edge_new[e_new].next=head[a]; head[a]=e_new++; } int judge(int a,int b){ if( p[a].et <= p[b].st || p[b].et <= p[a].st )return 1; return 0; } void getmap() { int i,j,a,b,c,d,t; for(j=i=0;i<m;i++){ scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&t); int t1=60*a+b; int t2=60*c+d; p[j].st=t1; p[j++].et=t1+t; p[j].st=t2-t; p[j++].et=t2; }n=j; e_num=0; memset(head,-1,sizeof(head)); for(i=0;i<m;i++){//共 m 场婚礼 for(j=0;j<m;j++){ if(i!=j){ if(judge(2*i,2*j)==0) AddEdge(2*i,2*j+1); if(judge(2*i,2*j+1)==0) AddEdge(2*i,2*j); if(judge(2*i+1,2*j)==0) AddEdge(2*i+1,2*j+1); if(judge(2*i+1,2*j+1)==0) AddEdge(2*i+1,2*j); } } } } stack <int>st; void tarjan(int x){ int i; tim[x]=low[x]=++vis_num; instack[x]=1; st.push(x); for(i=head[x];i!=-1;i=edge[i].next){ int u=edge[i].e; if(tim[u]==-1){ tarjan(u); if(low[x]>low[u])low[x]=low[u]; } else if(instack[u] && low[x]>tim[u])low[x]=tim[u]; } if(low[x]==tim[x]){ cnt++; do{ i=st.top(); st.pop(); instack[i]=0; belong[i]=cnt; }while(i!=x); } } void solve(){ int i; vis_num=cnt=0; memset(instack,0,sizeof(instack)); memset(belong,-1,sizeof(belong)); memset(tim,-1,sizeof(tim)); memset(low,0,sizeof(low)); for(i=0;i<n;i++){ if(tim[i]==-1)tarjan(i); } int flag=1; for(i=0;i<m;i++){ if(belong[2*i]==belong[2*i+1]){ flag=0;break; } /*****************************************/ //这里,为着色做准备,cf[x]保存的是和编号为 x 的连通分量矛盾的连通分量的编号 //在赵爽的论文中有证明,这样可以保证拓扑求解的可行性 //b 和 b' 所在的连通分量是矛盾的(如果不矛盾,那么2-SAT无解) cf[ belong[2*i] ]=belong[2*i+1]; cf[ belong[2*i+1] ]=belong[2*i]; /*****************************************/ } if(flag==0) printf("NO\n"); else{ printf("YES\n"); e_new=0; memset(head,-1,sizeof(head)); memset(indeg,0,sizeof(indeg)); for(i=0;i<e_num;i++){//缩点建图 if(belong[ edge[i].s ]!=belong[ edge[i].e ]){ AddEdge_new( belong[ edge[i].e ], belong[ edge[i].s ] );//反向边 indeg[belong[edge[i].s]]++;//统计入度 } } queue <int>q; for(i=1;i<=cnt;i++){ if(indeg[i]==0)q.push(i); } memset(col,0,sizeof(col)); while(!q.empty()){//拓扑排序求解 int cur=q.front(); q.pop(); if(col[cur]==0){//把未着色的点分别着不同颜色 col[cur]=1; col[cf[cur]]=-1; } for(i=head[cur];i!=-1;i=edge_new[i].next){ int u=edge_new[i].e; if(--indeg[u]==0)q.push(u); } } int a,b,c,d; for(i=0;i<m;i++){//选取一个颜色输出 if(col[ belong[2*i] ]==1){//如果颜色和所选颜色相同,输出点 b a=p[2*i].st/60; b=p[2*i].st%60; c=p[2*i].et/60; d=p[2*i].et%60; printf("%02d:%02d %02d:%02d\n",a,b,c,d); } else{//否则输出点 b' a=p[2*i+1].st/60; b=p[2*i+1].st%60; c=p[2*i+1].et/60; d=p[2*i+1].et%60; printf("%02d:%02d %02d:%02d\n",a,b,c,d); } } } } int main() { while(~scanf("%d",&m)) { getmap(); solve(); } return 0; }