一、基本算法
拓扑序列:对于一张有向图,求一个序列ai若对于每一条边(u,v),都满足au<=av ,则称这个序列为这张有向图的拓扑序列,一张图可能有多个拓扑序列。
求拓扑序列:找到入度为0的点,加入队列中,每次取出队列顶端的点加入拓扑序列的最后,将它到达的点的入度-1,然后再重复做,直到没有点的入度为0,若最后还是有点的入度大于0,则说明有向图中存在环。
代码:
void add(int x,int y) { num++; in[y]++; End[num]=y; Next[num]=Head[x]; Head[x]=num; } void topsort() { queue<int>q; for(int i=1;i<=n;i++) if(!in[i])q.push(i); while(!q.empty()) { int x=q.front();q.pop(); seq[++cnt]=x; for(int i=Head[x];i;i=Next[i]) { int y=End[i]; in[y]--; if(in[y]==0)q.push(y); } } }
二、应用
1.求字典序最小/最大的拓扑序列。
只需将上面代码中的队列换成小根堆/大根堆即可,每次取出堆顶,方法相同。
2.洛谷P1983车站分级
由于存在明显的优先级关系,所以考虑拓扑排序,按照小于的关系连边,然后拓扑排序一遍,找到最大的d[i]就是最少需要分的级别,因为这一段序列中的点必须满足级别严格递减。(再具体的题解可以看cellur的题解)
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int M=2e6; 4 int n,m,cnt=0,sta[2018],d[2018],in[2018],w[2018]; 5 bool v[2018]; 6 int num=0,end[M],next[M],head[2018]; 7 void add(int x,int y) 8 { 9 if(0)puts("AC"); 10 num++; 11 in[y]++; 12 end[num]=y; 13 next[num]=head[x]; 14 head[x]=num; 15 } 16 void topsort() 17 { 18 queue<int>q; 19 for(int i=1;i<=n+m;i++) 20 if(!in[i])q.push(i),d[i]=w[i]; 21 while(q.size()) 22 { 23 int x=q.front();q.pop(); 24 for(int i=head[x];i;i=next[i]) 25 { 26 int y=end[i]; 27 in[y]--; 28 d[y]=d[x]+w[y]; 29 if(in[y]==0)q.push(y); 30 } 31 } 32 } 33 int main() 34 { 35 cin>>n>>m; 36 for(int i=1;i<=n;i++) 37 w[i]=1; 38 for(int i=1;i<=m;i++) 39 { 40 memset(v,0,sizeof v); 41 cin>>cnt; 42 for(int j=1;j<=cnt;j++) 43 scanf("%d",&sta[j]),v[sta[j]]=1; 44 for(int j=sta[1];j<=sta[cnt];j++) 45 if(!v[j])add(j,n+i); 46 for(int j=1;j<=cnt;j++) 47 add(n+i,sta[j]); 48 } 49 topsort(); 50 int ans=0; 51 for(int i=1;i<=n;i++) 52 ans=max(ans,d[i]); 53 cout<<ans<<endl; 54 return 0; 55 }
3.洛谷P3243菜肴
最先想到的就是求出字典序最小的拓扑序列,然而我们很容易举出反例,如两个限制:5>2,4>3,这时的答案显然是1 5 2 4 3,而不是1 4 3 5 2,而后者的字典序小于前者。那么我们不妨换一种思路,在反图中求出字典序最大的序列,然后反着输出,如果遇到环就特判输出Impossible。这样为什么是对的呢?因为求最大的拓扑序的话就是在能放的中选择一个最大的放到最前面,如果不放这个而放次大的能放的,那么反过来之后,这个最大的一定在次大的前面,而本来它是可以放到次大的后面的,所以这样一定是最优的。
至于求字典序最大的拓扑序的方法在前面已经说过,这里用大根堆实现,直接看代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int M=1e5+10; 4 int T,n,m,in[M],seq[M]; 5 int num=0,cnt=0,Head[M],Next[M],End[M]; 6 void add(int x,int y) 7 { 8 num++; 9 End[num]=y; 10 Next[num]=Head[x]; 11 Head[x]=num; 12 } 13 void clear() 14 { 15 cnt=num=0; 16 for(int i=1;i<=n;i++) 17 in[i]=Head[i]=0; 18 for(int i=1;i<=m;i++) 19 Next[i]=End[i]=0; 20 } 21 bool topsort() 22 { 23 priority_queue<int>q;//大根堆 24 for(int i=n;i>=1;i--) 25 if(!in[i])q.push(i); 26 while(!q.empty()) 27 { 28 int x=q.top();q.pop(); 29 seq[++cnt]=x; 30 for(int i=Head[x];i;i=Next[i]) 31 { 32 int y=End[i]; 33 in[y]--; 34 if(in[y]==0)q.push(y); 35 } 36 } 37 for(int i=1;i<=n;i++) 38 if(in[i]>0)return 0; 39 return 1; 40 } 41 int main() 42 { 43 scanf("%d",&T); 44 while(T--) 45 { 46 scanf("%d%d",&n,&m); 47 clear(); 48 for(int i=1;i<=m;i++) 49 { 50 int x,y; 51 scanf("%d%d",&x,&y); 52 add(y,x); 53 in[x]++; 54 } 55 if(!topsort())puts("Impossible!"); 56 else{ 57 for(int i=n;i>=1;i--) 58 printf("%d ",seq[i]); 59 puts(""); 60 } 61 } 62 return 0; 63 }
4.HDU5222 Exploration
题意:给你一张混合图(既有有向边又有无向边),问有没有简单环。
先将无向边连接的点通过并查集合并成一个点,同时判断是否只通过无向边就能形成环,然后再加入有向边,注意添加的边的起始点为起始点所在集合编号,终点为终点所在集合编号,同时也要判断添加的起始点是否已经属于同一个集合,然后拓扑排序一遍,判断缩点后的有向图是否存在环,当然也可以用Tarjan做。注意多组数据一定要每次把邻接表也清零,因为这个WA了好多次。。。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 using namespace std; 5 const int M=1e6+10; 6 int T,n,m1,m2; 7 int fa[M],in[M]; 8 int num=0,Head[M],Next[M],End[M]; 9 int get(int x) 10 { 11 if(x==fa[x])return x; 12 else return fa[x]=get(fa[x]); 13 } 14 void Union(int x,int y) 15 { 16 fa[get(x)]=get(y); 17 } 18 void add(int x,int y) 19 { 20 num++; 21 End[num]=y; 22 Next[num]=Head[x]; 23 Head[x]=num; 24 } 25 void clear() 26 { 27 num=0; 28 for(int i=1;i<=n;i++) 29 fa[i]=i,in[i]=0,Head[i]=0; 30 for(int i=1;i<=m2;i++) 31 End[i]=Next[i]=0; 32 } 33 bool topsort() 34 { 35 queue<int>q; 36 for(int i=1;i<=n;i++) 37 if(fa[i]==i&&in[i]==0)q.push(i); 38 while(q.size()) 39 { 40 int x=q.front();q.pop(); 41 for(int i=Head[x];i;i=Next[i]) 42 { 43 int y=End[i]; 44 in[y]--; 45 if(in[y]==0)q.push(y); 46 } 47 } 48 for(int i=1;i<=n;i++) 49 if(fa[i]==i&&in[i]>0)return 1; 50 return 0; 51 } 52 int main() 53 { 54 scanf("%d",&T); 55 while(T--) 56 { 57 bool flag=0; 58 int x,y; 59 scanf("%d%d%d",&n,&m1,&m2); 60 clear(); 61 for(int i=1;i<=m1;i++)//加无向边 62 { 63 scanf("%d%d",&x,&y); 64 if(get(x)==get(y))flag=1; 65 //如果无向边就能构成环 66 else Union(x,y); 67 } 68 for(int i=1;i<=m2;i++) 69 { 70 scanf("%d%d",&x,&y); 71 int fx=get(x),fy=get(y); 72 if(fx==fy)flag=1; 73 add(fx,fy); 74 in[fy]++; 75 } 76 if(flag)puts("YES"); 77 else{ 78 bool f=topsort(); 79 if(f)puts("YES"); 80 else puts("NO"); 81 } 82 } 83 return 0; 84 }