poj 3683 Priest John's Busiest Day 【2-sat 经典建图输出一组解】【有向图tarjan + 反向拓扑 + 染色】

Priest John's Busiest Day
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 8652 Accepted: 2954 Special Judge

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 SiTi and DiSi 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

题意:一个小镇里面只有一个牧师,现在有些新人要结婚,需要牧师分别去主持一个仪式,给出每对新人婚礼的开始时间 s 和结束时间 t ,还有他们俩的这个仪式需要的时间(每对新人需要的时间长短可能不同) d ,牧师可以在婚礼开始的时间 d 内(s 到 s+d)或者是结束前的时间 d 内(t - d 到 t)完成这个仪式。现在问能否给出一种安排,让牧师能完成所有夫妇婚礼的仪式,如果可以,输出一种安排。

思路:用Xi表示第i个婚礼在开始时进行仪式,用!Xi表示第i个婚礼在结束时进行仪式。 

首先枚举婚礼 i 和 j (0 < i < N && 0 < j < i ),判断四种情况的时间是否相交

一:i 婚礼开始时进行仪式,j婚礼开始时进行仪式;时间相交则有 (Xi 析取Xj)

二:i 婚礼开始时进行仪式,j婚礼结束时进行仪式;时间相交则有 (Xi 析取!Xj)

三:i 婚礼结束时进行仪式,j婚礼开始时进行仪式;时间相交则有 (!Xi 析取 Xj)

四:i 婚礼结束时进行仪式,j婚礼结束时进行仪式;时间相交则有 (!Xi 析取 !Xj)

根据以上条件建图,然后tarjan求出SCC,判断出是否存在解后,需要输出一组解,这里的方法来自于赵爽 的《2-SAT 解法浅析》

核心算法实现:首先tarjan求强联通,缩点重新建图(这里建反向图),然后给图中的点着色,将一个未着色点 x 上色同时,把与它矛盾的点 y 以及 y 的所有子孙节点上另外一种颜色,上色完成后,进行拓扑排序,选择一种颜色的点输出就是一组可行解。这里可以采用一个fp[]数组建立关于Xi 与 !Xi

的关系,fp[Xi的SCC编号] = !Xi的SCC的编号 和 fp[!Xi的SCC的编号] = Xi的SCC的编号。这样的话,在拓扑排序染色时,就方便了很多。

AC代码:

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <algorithm> 
#define MAXN 2000+10
#define MAXM 4000000+100 
#define INF 100000000 
using namespace std;
struct Edge
{
	int from, to, next;
}edge[MAXM];
int head[MAXN], edgenum;
int st[MAXN], et[MAXN], D[MAXN];
int low[MAXN], dfn[MAXN];
int dfs_clock;
int sccno[MAXN], scc_cnt;
stack<int> S;
bool Instack[MAXN];
vector<int> G[MAXN];//存储新图 
int in[MAXN];//记录入度 
int fp[MAXN];//fp[i]记录   非i的SCC编号 
int color[MAXN];//着色 
int n;
void init()
{
	edgenum = 0;
	memset(head, -1, sizeof(head));
} 
void addEdge(int u, int v)
{
	Edge E = {u, v, head[u]};
	edge[edgenum] = E;
	head[u] = edgenum++;
}
bool judge(int s1, int e1, int s2, int e2)
{
	return !(s1 >= e2 || e1 <= s2);//时间区间重叠 
}
void getMap()
{
	int a, b, c, d;
	for(int i = 0; i < n; i++) 
	{
		scanf("%d:%d%d:%d%d", &a, &b, &c, &d, &D[i]);
		st[i] = a*60 + b;
		et[i] = c*60 + d;
	}
	for(int i = 0; i < n; i++)
	{
		for(int j = 0; j < i; j++)
		{
			if(judge(st[i], st[i] + D[i], st[j], st[j] + D[j]))//i,j在开始时进行 只能选一个 
			{
				addEdge(j, i + n);
				addEdge(i, j + n); 
			}
			if(judge(st[i], st[i] + D[i], et[j] - D[j], et[j]))//i在开始时进行和j在结束时进行 只能选一个 
			{
				addEdge(i, j);
				addEdge(j + n, i + n);
			} 
			if(judge(et[i] - D[i], et[i], st[j], st[j] + D[j]))//i在结束时进行和j在开始时进行 只能选一个 
			{
				addEdge(j, i);
				addEdge(i + n, j + n);
			} 
			if(judge(et[i] - D[i], et[i], et[j] - D[j], et[j]))//i,j在结束时进行 只能选一个 
			{
				addEdge(i + n, j);
				addEdge(j + n, i); 
			}
		}
	}
}
void tarjan(int u, int fa)
{
	int v;
	low[u] = dfn[u] = ++dfs_clock;
	S.push(u);
	Instack[u] = true;//犯傻了。。。写成false了 
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		v = edge[i].to;
		if(!dfn[v])
		{
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
		}
		else if(Instack[v])
		low[u] = min(low[u], dfn[v]);
	}
	if(low[u] == dfn[u])
	{
		scc_cnt++;
		for(;;)
		{
			v = S.top(); S.pop();
			Instack[v] = false;
			sccno[v] = scc_cnt;
			if(v == u) break; 
		} 
	}
} 
void find_cut(int l, int r)//求SCC 
{
	memset(low, 0, sizeof(low));
	memset(dfn, 0, sizeof(dfn));
	memset(sccno, 0, sizeof(sccno));
	memset(Instack, false, sizeof(Instack));
	dfs_clock = scc_cnt = 0;
	for(int i = l; i <= r; i++)
	if(!dfn[i]) tarjan(i, -1); 
} 
void suodian()
{
	for(int i = 1; i <= scc_cnt; i++) G[i].clear(), in[i] = 0; 
	for(int i = 0; i < edgenum; i++)
	{
		int u = sccno[edge[i].from];
		int v = sccno[edge[i].to];
		if(u != v)//缩点反向建图 
		G[v].push_back(u), in[u]++; 
	}
} 
void toposort()//拓扑排序 + 染色 
{
	queue<int> Q;
	for(int i = 1; i <= scc_cnt; i++) if(in[i] == 0) Q.push(i);//把入度为0的SCC入队 
	memset(color, 0, sizeof(color));//初始化 
	while(!Q.empty())//犯二了  。。。忘记! 了 
	{
		int u = Q.front();
		Q.pop();
		if(color[u] == 0)//着色  1为白色 2为黑色 
		{
			color[u] = 1;//u真时对应SCC着白色 
            color[fp[u]] = 2;//u假时对应SCC着黑色  
        }
		for(int i = 0; i < G[u].size(); i++)//去掉与u相关的边 将对应顶点入度减一 
		{
			int v = G[u][i];
			if(--in[v] == 0)//入度为0 入队 
			Q.push(v);
		}
	}
}
void solve()
{
	memset(fp, 0, sizeof(fp)); 
	for(int i = 0; i < n; i++)
	{
		if(sccno[i] == sccno[i+n])
		{
			printf("NO\n");
			return ;
		}
		else//分别建立当前点SCC编号 以及对应点SCC编号 之间的映射 
		{
		    fp[sccno[i]] = sccno[i+n];
			fp[sccno[i+n]] = sccno[i];
		}
	}
	printf("YES\n");
	suodian();//缩点反向建图 
	toposort();//拓扑排序 
	for(int i = 0; i < n; i++)
	{
		if(color[sccno[i]] == 1)//开始时进行特别仪式 
		printf("%02d:%02d %02d:%02d\n", st[i]/60, st[i]%60, (st[i] + D[i])/60, (st[i] + D[i])%60);
		else//结束时进行特别仪式 
		printf("%02d:%02d %02d:%02d\n", (et[i] - D[i])/60, (et[i] - D[i])%60, et[i]/60, et[i]%60);
	}
} 
int main()
{
	while(scanf("%d", &n) != EOF)
	{
		init();
		getMap();
		find_cut(0, 2*n-1);
		solve();
	}
	return 0;
} 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值