链接: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;
}