POJ 3683 Priest John's Busiest Day / OpenJ_Bailian 3788 Priest John's Busiest Day(2-sat问题)

POJ 3683 Priest John's Busiest Day / OpenJ_Bailian 3788 Priest John's Busiest Day(2-sat问题)

Description

John is the only priest in his town. September 1st is the John's busiest day in a year because there is an old legend in the town that the couple who get married on that day will be forever blessed by the God of Love. This year N couples plan to get married on the blessed day. The i-th couple plan to hold their wedding from time Si to time Ti. According to the traditions in the town, there must be a special ceremony on which the couple stand before the priest and accept blessings. The i-th couple need Di minutes to finish this ceremony. Moreover, this ceremony must be either at the beginning or the ending of the wedding (i.e. it must be either from Si to Si + Di, or from Ti - Di to Ti). Could you tell John how to arrange his schedule so that he can present at every special ceremonies of the weddings.

Note that John can not be present at two weddings simultaneously.

Input

The first line contains a integer N ( 1 ≤ N ≤ 1000).

The next N lines contain the Si, Ti and Di. Si and Ti are in the format of hh:mm.

Output

The first line of output contains "YES" or "NO" indicating whether John can be present at every special ceremony. If it is "YES", output another N lines describing the staring time and finishing time of all the ceremonies.

Sample Input

2

08:00 09:00 30

08:15 09:00 20

Sample Output

YES

08:00 08:30

08:40 09:00

Http

POJ:https://vjudge.net/problem/POJ-3683

OpenJ_Bailian:https://vjudge.net/problem/OpenJ_Bailian-3788

Source

2-sat

题目大意

给定n个活动及其最早开始、最迟结束及持续时间,要求活动要么在最早开始时开始,要么在最迟结束时结束。求如何安排这些活动使得这些活动都可以参加。

解决思路

对于每一个活动,因为都有两种时间安排方式,不妨将其看做2-sat中一组中的两个元素。用i表示在最早开始时开始,则用i+n表示最迟结束时结束。那么扫描所有的活动,若i与j冲突(即时间有重合),建边i->j+n,若i与j+n冲突,建边i->j,对于i+n与j的情况类似。然后tarjan缩点,判断可行性,若可行则继续,否则直接输出NO结束。至此,所有做法与这道题是一致的。

但是因为这道题要求输出结果而不只是判断是否成立,所以接下来我们要想办法找到一组解。为什么只是一组解呢?因为POJ非常良心的给了Special judge,所以不需要字典序。

整理一下思路,现在我们要讲讲2-sat比较深入的部分了!(咳咳……)

其实关于2-sat的建图,一直有一个比较显著的性质没有讲,那就是2-sat图的对称性

回顾一下我们建图的过程,我们说如果i与j矛盾,那么连边i->j',j->i'。注意这也就意味着若存在边x->y,则一定存在边y'->x'

推广一下,缩点后的边一定也具有对称性

这里借用一下伍昱 由对称性解2-sat问题.ppt中的图说明一下对称性:

一般情况:

POJ 3683 Priest John's Busiest Day / OpenJ_Bailian 3788 Priest John's Busiest Day(2-sat问题)

缩点后:

POJ 3683 Priest John's Busiest Day / OpenJ_Bailian 3788 Priest John's Busiest Day(2-sat问题)

更加一般的情况:

POJ 3683 Priest John's Busiest Day / OpenJ_Bailian 3788 Priest John's Busiest Day(2-sat问题)

所以说如果我们假设选择了i(不论是点还是强连通分量),那么就要将其对点i'标记为不选择,并且将不选择进行反向传递。

那么为了方便起见,我们在Tarjan缩点后将原图反置过来,这样就可以直接反着跑了。

最后一个问题是,按照怎样的一个顺序进行选择呢?

答案:拓扑排序的顺序(反过来的图)

因为在Tarjan缩点后,不会存在环了,所以可以按照拓扑序列依次选择。

另外需要注意的是,Tarjan缩点后的强连通分量的点不具备对称性,需要用另一个数组(我的代码里用的是Opp)存下与之相对的点的强联通编号,这样才能进行有关对称性的操作。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<stack>
#include<queue>
using namespace std; class TIME//记录开始时间和结束时间
{
public:
int t1,t2;
}; const int maxN=4000;
const int inf=2147483647; int n;
TIME T[maxN];
vector<int> E[maxN];//原图
vector<int> E2[maxN];//缩点并反向后的图
//tarjan Tarjan中要用到的变量,不解释
int cnt=0;
int dfn[maxN];
int low[maxN];
bool instack[maxN];
stack<int> S;
//LTK 连通块
int LTKcnt=0;//连通块数量
int Belong[maxN];//原来的点所属的强联通分量编号
int Opp[maxN];//存下与x所属的强连通分量对立的x'所属的强连通分量的编号
//Top_sort
int InDegree[maxN];//入读
queue<int> Q;
int color[maxN];//标记选择或不选择,-1表示还未进行标记,0表示不选择,1表示选择 void Read_and_Init();
bool Repeat(int a,int b);//判断时间是否重复
void Link(int a,int b);//连接a和b
void tarjan(int u);
void Top_sort();//拓扑排序 int main()
{
while(cin>>n)
{
Read_and_Init();
memset(dfn,0,sizeof(dfn));
memset(instack,0,sizeof(instack));
for (int i=1;i<=n*2;i++)
if (dfn[i]==0)
tarjan(i); bool ok=1;
for (int i=1;i<=n;i++)
{
if (Belong[i]==Belong[i+n])//判断可行性
{
ok=0;
break;
}
Opp[Belong[i]]=Belong[i+n];//同时存下缩点后相对的点编号
Opp[Belong[i+n]]=Belong[i];
}
if (ok)
{
cout<<"YES"<<endl;
Top_sort();
for (int i=1;i<=n;i++)
if (color[Belong[i]]==1)//如果第i个活动选择最早开始是开始
{
printf("%.2d:%.2d %.2d:%.2d\n",T[i].t1/60,T[i].t1%60,T[i].t2/60,T[i].t2%60);
}
else //if (color[Belong[i]]==0) 否则则是最晚结束时结束
printf("%.2d:%.2d %.2d:%.2d\n",T[i+n].t1/60,T[i+n].t1%60,T[i+n].t2/60,T[i+n].t2%60);
}
else
cout<<"NO"<<endl;
}
return 0;
} void Read_and_Init()
{
int a,b,c,d,e;
//cin>>n;
for (int i=1;i<=n*2;i++)
{
E[i].clear();
E2[i].clear();
}
cnt=0;
LTKcnt=0;
while (!Q.empty())
Q.pop();
while (!S.empty())
S.pop();
for (int i=1;i<=n;i++)
{
scanf("%d:%d %d:%d %d",&a,&b,&c,&d,&e);//注意转换格式
a=a*60+b;
c=c*60+d;
T[i].t1=a;
T[i].t2=a+e;
T[i+n].t1=c-e;
T[i+n].t2=c;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i!=j)//判断两个活动是否冲突
{
if (Repeat(i,j))
Link(i,j+n);
if (Repeat(i,j+n))
Link(i,j);
if (Repeat(i+n,j))
Link(i+n,j+n);
if (Repeat(i+n,j+n))
Link(i+n,j);
}
return;
} bool Repeat(int a,int b)
{
if ((T[a].t1>=T[b].t2)||(T[a].t2<=T[b].t1))
return 0;
return 1;
} void Link(int a,int b)
{
E[a].push_back(b);
} void tarjan(int u)
{
cnt++;
dfn[u]=low[u]=cnt;
instack[u]=1;
S.push(u);
for (int i=0;i<E[u].size();i++)
{
int v=E[u][i];
if (dfn[v]==0)
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
if (instack[v]==1)
low[u]=min(low[u],dfn[v]);
}
if (dfn[u]==low[u])
{
int v;
LTKcnt++;
do
{
v=S.top();
S.pop();
instack[v]=0;
Belong[v]=LTKcnt;
}
while (u!=v);
}
return;
} void Top_sort()
{
memset(InDegree,0,sizeof(InDegree));
memset(color,-1,sizeof(color));
for (int i=1;i<=n*2;i++)//将原图缩点后反置
for (int j=0;j<E[i].size();j++)
{
int v=E[i][j];
if (Belong[i]!=Belong[v])
{
E2[Belong[v]].push_back(Belong[i]);
InDegree[Belong[i]]++;//同时统计入度(拓扑排序要用)
}
}
for (int i=1;i<=LTKcnt;i++)
if (InDegree[i]==0)
Q.push(i);//把入度为0的加入队列
do
{
int u=Q.front();
Q.pop();
if (color[u]==-1)//如果这个点还没有标记,则把其标记为1(选择),对立点标记为0(不选择)
{
color[u]=1;
color[Opp[u]]=0;
}
for (int i=0;i<E2[u].size();i++)
{
InDegree[E2[u][i]]--;
if (InDegree[E2[u][i]]==0)
Q.push(E2[u][i]);//加入新的点
}
}
while (!Q.empty());
return;
}
上一篇:GO 和 KEGG 的区别 | GO KEGG数据库用法 | 基因集功能注释 | 代谢通路富集


下一篇:最简单的android自定义进度条样式