HDU 1811 Rank of Tetris(并查集+拓扑排序)
http://acm.hdu.edu.cn/showproblem.php?pid=1811
题意:
给你N个点(编号从0到N-1)和M个关系,要你判断这个图的所有点的顺序是否可以全部确定.不过对于任意点的关系可能存在A>B或A<B或A=B三种情况,如果A=B的话,那么就比较他们的编号,编号大的点分数大.(如果A=B且A编号>B编号,那么A分数大).
分析:
输出结果有三种情况:缺少信息(即可拓扑排序),OK(能全排列),冲突(产生有向环).
我们先按普通拓扑排序的过程来处理.
假设A>B或B<A,直接在A与B之间加一条有向边即可.
假设A=B呢?由于A与B的序号不同,所以我们最终还是可以在A与B间加一条有向边.
但是如果接下来又出现C=A呢?不仅C与A之间要加边,C与B之间也要加边的.甚至C与和A相等的所有点之间都要加边.
如果我们能做到上面的步骤,这个图就建立出来了.不过这个图就太复杂了.其实我们可以这么想,对于A=B=C=…=E这些(Rating相等但编号不等)都相等的边,他们之间肯定能分出确定的顺序的(因为它们编号肯定都不同).
重点来了:我们把所有具有=关系的点都聚合成1点(合并为一个连通分量,且只用该分量的根节点代替分量中的所有点).然后我们只处理>或<关系(添加两个分量根之间的有向边).然后我们只需要判断这个缩减的图是否能全排序或拓扑排序或冲突即可.
如何判断一个有向图是否能全排序或拓扑排序或冲突呢?按照topo()函数的处理过程来.如果在队列从Q队列出去的0入度点==所有连通分量的根数目,那么这个图肯定没冲突.
假设没冲突,且在队列Q中始终只有1个元素的话,说明该图可全排序.否则只能拓扑排序.
注意:题目中还有一个问题,我们必须保证所有具有=关系的点不冲突.(即不会出现A=B,之后又来一个A>B关系)这点在代码中有体现.
AC代码:
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=10000+10;
const int maxm=20000+10;
int n,m;
int fa[maxn];
int find(int i)
{
if(fa[i]==-1) return i;
return fa[i]=find(fa[i]);
}
int in[maxn],head[maxn];
struct Edge
{
int to,next;
}edges[maxm]; //边从0保存
int tot; //计数边总数
void add_edge(int from,int to)
{
edges[tot]=(Edge){to,head[from]};
head[from]=tot++;
in[to]++;
}
int topo()
{
queue<int> Q;
int fa_num=0; //连通分量根数目
int cnt=0; //入度为0且从Q出队列的点数
bool all_order=true;//是否可全排序标志
for(int i=0;i<n;i++)if(find(i)==i)
{
fa_num++;
if(in[i]==0) Q.push(i);
}
while(!Q.empty())
{
if(Q.size()>1) all_order=false;
int u=Q.front(); Q.pop();
cnt++;
for(int e=head[u];e!=-1;e=edges[e].next)
{
int v=edges[e].to;
if(--in[find(v)]==0)
Q.push(find(v));
}
}
if(cnt<fa_num) return -1; //冲突
if(all_order) return 1; //可全排序
return 0; //可拓扑排序
}
char str[maxm][5];
int a[maxm],b[maxm];
int main()
{
while(scanf("%d%d%*c",&n,&m)==2)
{
memset(head,-1,sizeof(head));
memset(fa,-1,sizeof(fa));
memset(in,0,sizeof(in));
tot=0;
int ans=0; //保存结果状态,-1表冲突,0表拓扑排序,1表全排序
for(int i=0;i<m;i++) //读取关系,合并连通分量
{
scanf("%d%s%d",&a[i],str[i],&b[i]);
if(str[i][0]=='=')
{
int u=a[i], v=b[i];
u=find(u), v=find(v);
if(u!=v) fa[u]=v;
}
}
for(int i=0;i<m;i++) //添加有向边,且判断连通分量中是否冲突
{
int u=a[i], v=b[i];
if(find(u)==find(v) && str[i][0]!='=')
{
ans=-1; //冲突
break;
}
if(str[i][0]=='<') add_edge(find(v),find(u));
else if(str[i][0]=='>') add_edge(find(u),find(v));
}
if(ans==0) ans=topo();
if(ans==-1) printf("CONFLICT\n");
else if(ans==0) printf("UNCERTAIN\n");
else if(ans==1) printf("OK\n");
}
return 0;
}