这两道题都需要用到拓扑排序,所以先介绍一下什么叫做拓扑排序。
这里说一下我是怎么理解的,拓扑排序实在DAG中进行的,根据图中的有向边的方向决定大小关系,具体可以下面的题目中理解其含义
Educational Codeforces Round 25 E. Minimal Labels
题目链接:http://codeforces.com/contest/825/problem/E
题意:给你一个有向无环图(DAG),有n个顶点,m条边,顶点的序号分别是1-n,现在给你1-n的数对n个顶点进行赋值,赋值有一定的要求让如果有一条边是x->y,那么x的权值小于y,最后,如果有多种赋值方式输出字典序最小的方式,这个题的图可以是由几个连通块组成。
一开始不会做,后来听说是拓扑排序裸题,所以就去学了一下,还是很简单的,关键在于这道题需要反向建图,直接上代码
//Author: xiaowuga #include <bits/stdc++.h> #define maxx INT_MAX #define minn INT_MIN #define inf 0x3f3f3f3f const long long N=200000+10; using namespace std; typedef long long LL; vector<int>q[N]; priority_queue<int>p;//优先队列,用来维护字典序 int main() { int n,m; scanf("%d%d",&n,&m); int in[N]={0}; for(int i=0;i<m;i++){ int x,y; scanf("%d%d",&x,&y); q[y].push_back(x);//反向建图 in[x]++;//记录每个点出度 } for(int i=1;i<=n;i++) if(in[i]==0) p.push(i);//在剪边前,把出度为0(叶子节点)的点压入堆 int now=n,ans[N];
//由于优先队列是大顶堆,每次回选出当前堆里最大的元素,出来赋值,保证了字典序最小 while(!p.empty()){ int t=p.top();p.pop();// ans[t]=now--; for(int i=0;i<q[t].size();i++){ if(--in[q[t][i]]==0) p.push(q[t][i]);//剪边,将剪完边以后出度为0的顶点压入堆 } } for(int i=1;i<=n;i++){ printf("%d ",ans[i]); } cout<<endl; return 0; }
这道题的关键是在于需要反向建图,原因是它需要一个点所有边的弧尾都标完号才能确定这个点的标号,意思就是每个点的后续节点都赋值了以后我才能对这个点赋值(因为这个点的赋值要比他的所有后续节点要小)。所以后续节点决定他前序节点的赋值,所以我们通过反向建图,从后往前拓扑排序,用优先队列维护字典序,并一路赋值,就可以AC了。
HDU1285
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1285
题意:中文问题,懒的解释,自己看吧,题目已经写得很清楚了。与上一道题不同的是这道题正向建图,直接上代码。
//Author: xiaowuga #include <bits/stdc++.h> #define maxx INT_MAX #define minn INT_MIN #define inf 0x3f3f3f3f const long long N=517; using namespace std; typedef long long L; int p[N][N]; int in[N]; priority_queue<int,vector<int>,greater<int> > q; int main() { ios::sync_with_stdio(false);cin.tie(0); int n,m; while(cin>>n>>m){ memset(p,0,sizeof(p)); memset(in,0,sizeof(in)); while(!q.empty()) q.pop(); for(int i=0;i<m;i++){ int x,y; cin>>x>>y; if(p[x][y]==0){ p[x][y]=1; in[y]++;//记录点的入度 } } for(int i=1;i<=n;i++) if(in[i]==0) q.push(i);//将入度为0的点压入堆 int ct=1; while(!q.empty()){ int t=q.top();q.pop(); if(ct!=n){ cout<<t<<" "; ct++; } else cout<<t<<endl; for(int i=1;i<=n;i++){ if(p[t][i]==0) continue; if(--in[i]==0) q.push(i);//自树根到树叶的剪边 } } } return 0; }
这道题需要正向建图原因是
因为它需要一个点所有指向它的所有边的弧首都标完号才能确定这个点的标号,意思就是一个点所有的前续节点的排名确定了以后,才可以确定这个点的排名,排名高的序号先输出。所以是一个前续节点决定后续节点的关系,所以我们正向建图,同样利用优先队列维护一个字典序