POJ - 3683 Priest John's Busiest Day(2-SAT模板)

链接:http://poj.org/problem?id=3683

题意:有n个婚礼需要主持。每个婚礼有两个时间段可以主持,求一个时间不冲突的主持n个婚礼的时间安排方案。

思路:显然是个2-SAT题,若将每个婚礼的两个时间段视为一个集合的两个元素,问题转化为从n个集合里选n个元素,并且某些元素有互斥关系。很明显的2-SAT问题。先按选谁必须选谁的方式构图,然后tarjan缩点。缩完点后,如果有集合的两个元素在同一强连通分量中,则无解。否则,有两种寻找方案的方法。第一种是将获得的缩点之间建反向边,拓扑排序的同时进行标记。即拓扑排序到u点时,如果u点没确定选不选,那就选(结论,记住即可。),并将其集合的另一个元素所在的缩点标记为不选。第二种方法,直接比较一个集合两个元素tarjan缩点的点值,谁的值更小,说明选其影响更小(这也是反向拓扑排序的原因)。

PS:改了半天(是真的半天),最后发现建原图的时候,选择的方式错了,应该按选谁必选谁的方式,我按的是不选谁必不选谁的方式。。。。。。。。。。。。。。。。。MD。。。。。。。心累。。。。。。。还是自己没理解算法。。。。。。。。。。。。。。。。。

第一种:

#include <cstdio>
#include <queue> 
#define ll long long
using namespace std;
const int N = 2e3+10;
const int M = 5e6+10;
int n;
bool no; 
struct node { int l,r; }weds[N];
struct	Edge { int to,nxt; };
struct Two_Sat
{
	int in[N],oppo[N],head1[N],cnt1,head[N],cnt,dfn[N],id,low[N],color[N],cl,sta[N],top,book[N];; 
	//in[i]:缩点i的入度
	//oppo[i]:点i所在集合另一元素所在的缩点值
	//color[i]:点i所在缩点值 
	bool vis[N];
	struct Edge G[M],G1[M];
	inline void init()//初始化 
	{
		no=0;
		cl=cnt=cnt1=id=top=0;
		for(int i=0;i<2*n;i++)	in[i]=vis[i]=dfn[i]=0,book[i]=head[i]=head1[i]=-1;
	}
	inline void add(int u,int v)
	{
		G[cnt].to=v,G[cnt].nxt=head[u],head[u]=cnt++;
	}
	inline void add1(int u,int v)
	{
		G1[cnt1].to=v,G1[cnt1].nxt=head1[u],head1[u]=cnt1++;
	}
	inline void tarjan(int u)//tarjan强连通分量缩点 
	{
		int v;
		dfn[u]=low[u]=++id; vis[u]=1; sta[++top]=u;
		for(int i=head[u];i!=-1;i=G[i].nxt)
		{
			v=G[i].to;
			if(!dfn[v])
			{
				tarjan(v);
				low[u]=min(low[u],low[v]);
			}
			else if(vis[v])
				low[u]=min(low[u],low[v]);
		}
		if(low[u]==dfn[u])
		{
			color[u]=++cl;
			while(sta[top]!=u)
			{
				color[sta[top]]=cl; vis[sta[top--]]=0;			
			}
			vis[sta[top--]]=0;
		}
	}
	inline void Tarjan()
	{
		for(int i=0;i<2*n;i++)
			if(!dfn[i]) tarjan(i);		
	}
	inline void SetOppo()//确定每个元素的同集合元素的缩点值
	{
		for(int i=0;i<2*n;i+=2)
		{
			if(color[i]==color[i^1]) { no=1; break; }
			oppo[color[i]]=color[i^1],oppo[color[i^1]]=color[i];			
		}		
	}
	inline bool topo()//拓扑排序,同时确定确定选哪个 
	{
		int u,v;
		//先建缩点反向图 
		for(u=0;u<2*n;u++)
			for(int i=head[u];i!=-1;i=G[i].nxt)
			{
				v=G[i].to;
				if(color[v]==color[u]) continue;
				add1(color[v],color[u]),in[color[u]]++;
			}
		queue<int> q;
		for(int i=1;i<=cl;i++) if(!in[i]) q.push(i);
		while(!q.empty())
		{
			u=q.front(); q.pop();
			//没有确定选不选就一定选
			if(book[u]==-1) book[u]=1,book[oppo[u]]=0; 	
			for(int i=head1[u];i!=-1;i=G1[i].nxt)
			{
				v=G1[i].to;
				if(--in[v]==0) q.push(v);
			}
		}
		return 1;
	}
	inline void print()
	{
		for(int i=0;i<2*n;i++)
		{
			if(book[color[i]]==1)
				printf("%02d:%02d %02d:%02d\n",weds[i].l/60,weds[i].l%60,weds[i].r/60,weds[i].r%60);
		}		
	}
}two;
inline bool ok(node a,node b)
{
	if(a.r<=b.l||b.r<=a.l) return 1;
	return 0;
}
int main(void)
{
	int hl,ml,hr,mr,d;
	while(~scanf("%d",&n))
	{
		two.init();
		for(int i=0;i<2*n;i+=2)
		{
			scanf("%02d:%02d %02d:%02d %d",&hl,&ml,&hr,&mr,&d);
			weds[i].l=hl*60+ml; weds[i].r=weds[i].l+d;
			weds[i^1].r=hr*60+mr; weds[i^1].l=weds[i^1].r-d; 	
		}
		for(int i=0;i<2*n;i+=2)
			for(int j=i+2;j<2*n;j+=2)
			{
				if(!ok(weds[i],weds[j]))
					two.add(i,j^1),two.add(j,i^1);	
				if(!ok(weds[i],weds[j^1]))
					two.add(i,j),two.add(j^1,i^1);	
				if(!ok(weds[i^1],weds[j]))
					two.add(i^1,j^1),two.add(j,i);	
				if(!ok(weds[i^1],weds[j^1]))
					two.add(i^1,j),two.add(j^1,i);	
			}	
		two.Tarjan();
		two.SetOppo();
		if(no){ printf("NO\n"); continue;}  
		printf("YES\n");
		two.topo();
		two.print();
	}
	return 0;	
} 

第二种:

#include <cstdio>
#include <queue> 
#define ll long long
using namespace std;
const int N = 2e3+10;
const int M = 5e6+10;
int n;
bool book[N];
bool no; 
struct node { int l,r; }weds[N];
struct	Edge { int to,nxt; };
struct Two_Sat
{
	int in[N],oppo[N],head1[N],cnt1,head[N],cnt,dfn[N],id,low[N],color[N],cl,sta[N],top; 
	//in[i]:缩点i的入度
	//oppo[i]:点i所在集合另一元素所在的缩点值
	//color[i]:点i所在缩点值 
	bool vis[N];
	struct Edge G[M],G1[M];
	inline void init()//初始化 
	{
		no=0;
		cl=cnt=cnt1=id=top=0;
		for(int i=0;i<2*n;i++)	book[i]=in[i]=vis[i]=dfn[i]=0,head[i]=head1[i]=-1;
	}
	inline void add(int u,int v)//原图建边 
	{
		G[cnt].to=v,G[cnt].nxt=head[u],head[u]=cnt++;
	}
	inline void add1(int u,int v)//缩点后,反向建图 
	{
		G1[cnt1].to=v,G1[cnt1].nxt=head1[u],head1[u]=cnt1++;
	}
	inline void tarjan(int u)//tarjan强连通分量缩点 
	{
		int v;
		dfn[u]=low[u]=++id; vis[u]=1; sta[++top]=u;
		for(int i=head[u];i!=-1;i=G[i].nxt)
		{
			v=G[i].to;
			if(!dfn[v])
			{
				tarjan(v);
				low[u]=min(low[u],low[v]);
			}
			else if(vis[v])
				low[u]=min(low[u],low[v]);
		}
		if(low[u]==dfn[u])
		{
			color[u]=++cl;
			while(sta[top]!=u)
			{
				color[sta[top]]=cl; vis[sta[top--]]=0;			
			}
			vis[sta[top--]]=0;
		}
	}
	inline void Tarjan()
	{
		for(int i=0;i<2*n;i++)
			if(!dfn[i]) tarjan(i);		
	}
	inline void SetOppo()//确定每个元素的同集合元素的缩点值
	{
		for(int i=0;i<2*n;i+=2)
		{
			if(color[i]==color[i^1]) { no=1; break; }//其实这种情况不存在 
			oppo[color[i]]=color[i^1],oppo[color[i^1]]=color[i];			
		}		
	}
	inline bool topo()
	{
		int u,v;
		//建缩点反向图 
		for(u=0;u<2*n;u++)
			for(int i=head[u];i!=-1;i=G[i].nxt)
			{
				v=G[i].to;
				if(color[v]==color[u]) continue;
				add1(color[v],color[u]),in[color[u]]++;
			}
		queue<int> q;
		for(int i=1;i<=cl;i++) if(!in[i]) q.push(i);
		
		while(!q.empty())
		{
			u=q.front(); q.pop();
			//没有确定选不选就一定选 
			if(book[u]==-1) book[u]=1,book[oppo[u]]=0; 	
			for(int i=head1[u];i!=-1;i=G1[i].nxt)
			{
				v=G1[i].to;
				if(--in[v]==0) q.push(v);
			}
		}
		return 1;
	}
	inline void solve()
	{
		//根据强连通分量的缩点值,确定选谁。 
		for(int i=0;i<2*n;i+=2)
		{
			if(color[i]<color[i^1]) book[i]=1;
			else  book[i^1]=1;
		}		
	}
	inline void print()
	{
		for(int i=0;i<2*n;i++)
		{
			if(book[i])
				printf("%02d:%02d %02d:%02d\n",weds[i].l/60,weds[i].l%60,weds[i].r/60,weds[i].r%60);
		}		
	}
	
}two;
inline bool ok(node a,node b)
{
	if(a.r<=b.l||b.r<=a.l) return 1;
	return 0;
}
int main(void)
{
	int hl,ml,hr,mr,d;
	while(~scanf("%d",&n))
	{
		two.init();
		for(int i=0;i<2*n;i+=2)
		{
			scanf("%02d:%02d %02d:%02d %d",&hl,&ml,&hr,&mr,&d);
			weds[i].l=hl*60+ml; weds[i].r=weds[i].l+d;
			weds[i^1].r=hr*60+mr; weds[i^1].l=weds[i^1].r-d; 	
		}
		//建原图 
		for(int i=0;i<2*n;i+=2)
			for(int j=i+2;j<2*n;j+=2)
			{
				if(!ok(weds[i],weds[j]))
					two.add(i,j^1),two.add(j,i^1);	
				 				
				if(!ok(weds[i],weds[j^1]))
					two.add(i,j),two.add(j^1,i^1);	
				
				if(!ok(weds[i^1],weds[j]))
					two.add(i^1,j^1),two.add(j,i);	 
				if(!ok(weds[i^1],weds[j^1]))
					two.add(i^1,j),two.add(j^1,i);	 
			}
		two.Tarjan();	
		two.SetOppo();
		if(no){ printf("NO\n"); continue;}  
		printf("YES\n");
		two.solve();
		two.print();
	}
	return 0;	
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值