【图论】拓扑排序专题训练

题单介绍

首先,有这个题单是因为 NOIP 2020 的第一题考到了拓扑排序,并且似乎题单区并没有这个题单的说( 其次,也写了学习笔记,也当自己的任务计划训练一下吧。 目前是 13 道题,如果再看到值得刷的题目还会新增,欢迎私信。 ## 题目简述 - P4017 最大食物链计数:很板子的题 - P6145 [USACO20FEB]Timeline G:差分约束$\to$ DAG,拓扑排序(SPFA 亦可) - P2419 [USACO08JAN]Cow Contest S:不是很显然的感觉,拓扑排序+并查集 - P7113 排水系统:拓扑排序+分数处理 - P1960 郁闷的记者:板板板板板 - P1137 旅行计划:先拓扑排序再 DP - P6154 游走:期望,拓扑排序 ## All Problems - [P4017 最大食物链计数](https://www.luogu.com.cn/problem/P4017) - [P2712 摄像头](https://www.luogu.com.cn/problem/P2712) - [P6145 [USACO20FEB]Timeline G](https://www.luogu.com.cn/problem/P6145) - [P1960 郁闷的记者](https://www.luogu.com.cn/problem/P1960) - [P2419 [USACO08JAN]Cow Contest S](https://www.luogu.com.cn/problem/P2419) - [P1137 旅行计划](https://www.luogu.com.cn/problem/P1137) - [P6154 游走](https://www.luogu.com.cn/problem/P6154) - [P1038 [NOIP2003 提高组] 神经网络](https://www.luogu.com.cn/problem/P1038) - [P4316 绿豆蛙的归宿](https://www.luogu.com.cn/problem/P4316) - [P1347 排序](https://www.luogu.com.cn/problem/P1347) - [P3275 [SCOI2011]糖果](https://www.luogu.com.cn/problem/P3275) - [P4742 [Wind Festival]Running In The Sky](https://www.luogu.com.cn/problem/P4742) - [P3243 [HNOI2015]菜肴制作](https://www.luogu.com.cn/problem/P3243) ----- $$\Large\color{cadetblue}\boxed{\textbf{从平面到线性:拓扑排序の降维打击}}$$ $$\small\underline{\rm Topological\ Sort:The\ Progress\ of\ Dimensionality\ Reduction}$$ 事实上,拓扑排序就是将一个图变化为一个线性序列的过程。 故,我愿称之为降维打击。 ## 壹:初识 我们依然按照网上一贯的方式引入,假设一中有以下 $n$ 个课程,在这个例子中我们令 $n=5$。 - $1.$ 珂学专项研究 $\rm Null$ - $2.$ 物理竞赛 $1,5$ - $3.$ 生物竞赛 $1$ - $4.$ 信息学电竞 $1,2,5$ - $5.$ 修电脑 $1$ 在每一项前的数字是他们的编号,而在他们后面的数字则是 **前置课程**,换言之,要想学 $x$ 课程,就必须把他所有的前置课程学完(其中如果是 $\Null$ 则没有前置课程)。那么,我们需要安排出一个学习顺序,怎么办呢? 先随便选一个试试看吧!看着信息学电竞就觉得很有意思诶,看看:要先学这么多??那看看前置课程中的修电脑罢……诶,也有前置课程?看看前置课程珂学研究——终于没有前置课程了!可以从这里开始学。那么,**没有前置课程** 的内容就是我们要先开始学的内容了。 那么,我们可以把**所有**没有前置课程的内容先学完。这里有一个重要的“删除”操作:如果我学过了 $x$ 课程,那所有课程中,如果有前置课程为 $x$ 的,可以把这个前置课程删掉了。为什么呢?因为已经学过了,相当于不再起作用;换言之,我们安排过学习顺序的课程就无需再学了,于是可以删掉。 那么学完珂学研究之后,课程变成了这样: - $2.$ 物理竞赛 $5$ - $3.$ 生物竞赛 $\rm Null$ - $4.$ 信息学电竞 $2,5$ - $5.$ 修电脑 $\rm Null$ 这时候我们发现 $3,5$ 都可以学了(没有前置课程),那么就学吧!学完之后,课程就变成了这样: - $2.$ 物理竞赛 $\rm Null$ - $4.$ 信息学电竞 $2$ 诶,$2$ 已经没有前置课程了!此时可以学习第二个课程,再学完后就变成了这样: - $4.$ 信息学电竞 $\rm Null$ 那么就可以学习信息学了!这是最后一个课程;至此,我们学课程的顺序如下: $$\rm Order=\{1,3,5,2,4\}$$ 对于这样的一个可行的顺序,我们称之为“拓扑序”,由于在同时有多个前置为零的课程时,选任何一个都可以(不影响结果),所以拓扑序可能有多种多样的结果,这不影响我们选课(对吧)。 诶,已经都没有前置课程了!那么就都学吧,先学前面的或者后面的都不影响(想一想,为什么),所以一种可行的学课程顺序是这样的: $$\rm Order=\{1,3,5,2,4\}$$ 这样的一个可行的顺序,我们称之为“拓扑序”,由于在同时有多个前置课程时,选任何一个都可以(不影响结果),所以拓扑序可能有多种多样的结果,这不影响我们选课(对吧)。 那么我们可以把这个过程抽象成一个图论的模型: - **前置课程** $\to$ **入度** - **课程** $\to$ **节点** 那么上面的课程就可以抽象成这样(箭头从前置课程指向下一个课程): ![](https://cdn.luogu.com.cn/upload/image_hosting/tm4swhsb.png) 那么我们学完珂研之后就变成了这样: ![](https://cdn.luogu.com.cn/upload/image_hosting/b4ghd6d5.png) 接下来的过程也显然简单不少(以此类推就行了)。 但是不如设想一个极其简单的场面(纯属虚构): - “要想学线段树先学树状数组” - “要想学树状数组先学线段树” 那你会发现,当你想学那个常数大的东西时要先学树状数组,想学树状数组又要学线段树……这不是陷入死循环了么?其实,当这个图**存在环**时是不能进行拓扑排序的(原因如上),所以,能跑拓扑排序的一定是一个无环图,而且还得是一个有向图,那么,综合起来就是: $$\small\textbf{有向无环图(DAG, Directed Acyclic Graph)}$$ ----- ## 贰:实现 关键在于,哪些点是 **入度为 $0$** 的呢? 我们的关键在于**维护一个入度为零的集合**。 当我们要删除一个节点 $x$ 的时候,就要进行“删除”操作,即删除和 $x$ 相关的所有边——事实上,由于我们对 $x$ 进行了操作,所以他必然入度为 $0$,删除的边全部都是出边。 这完了么?其实并没有——当你删除这些边时,他们指向的节点入度就减一了,这时,我们要把入度减一后为 $0$ 的节点放到集合中,这就成功地维护了一个集合。 那么我们来做一下例题吧。 ----- ### [HDU 1285 确定比赛名次](http://acm.hdu.edu.cn/showproblem.php?pid=1285) 以下为不改变题意的简化版描述。 有 $n$ 个编号为 $1,2\cdots,n$ 的队伍($1\le n\le 500$),现要将他们从前往后排名,但不能直接获得每个队的比赛成绩,只知道每场比赛的结果,形如 $P_1$ 赢 $P_2$,意味着 $P_1$ 排名在 $P_2$ 前,求比赛的名次。 多测,每组第一行为 $n,m$,表示有 $n$ 个队 $m$ 场比赛,接下来 $m$ 行每行两个整数 $P_1,P_2$ 即 $P_1$ 赢了 $P_2$。 要求给出一个合理的排名,输出时队伍号之间有空格,最后一名后面没有空格。 其他说明:排名可能不唯一,故要求输出时编号小的队伍在前;输入数据保证有解。 Sample Input ~~~ 4 3 1 2 2 3 4 3 ~~~ Sample Output ~~~ 1 2 4 3 ~~~ 抽象化是一个重要的工具。 这时候我们考虑:如果 $P_1$ 胜过了 $P_2$,就划定一条从胜者指向败者的边,这样子,只要入度为 $0$ 就一定是“没有输过的人”,即第一名,接着按拓扑排序的思想来就行力。由于我们可以再找入度为 $0$ 节点时从前往后找,也可以保证“从小到大”这一条件。 ~~你说这出题人为了省 SPJ 就麻烦做题人呗~~ 那么我们约定一下变量名字: - $in$ 存储入度,$in_x$ 即 $x$ 节点的入度(前驱数量) - $mp$ 存储边(布尔类型),$mp_{(x,y)}$ 即 $x$ 和 $y$ 是否存在一条边 - $n,m$ 如题意所示。 - $q$ 存储拓扑排序路径的队列。 那么我们需要循环找到**入度为零**且**编号最小**的节点 $s$,入队。然后就可以处理:首先将其入队并把其入度赋值为 $-1$(想一想,为什么),然后再把前驱为他的所有节点入度减一,并删去相连的边。 多测记得清空,不知道为啥要防重边,鉴于细节有点多,还是放一下参考代码供大家对拍啥的。 ```cpp #include<bits/stdc++.h> using namespace std; bool mp[505][505];//存边(是否存在) int in[505],n,m;//存入度,以及两个变量 queue<int>q;//存拓扑排序后路径 void print(){ //鉴于奇怪的输出要求,决定写个输出函数 printf("%d",q.front()); //输出第一个 q.pop();//出队 while(!q.empty()){ printf(" %d",q.front());//输出 q.pop();//出队 }puts("");//换行 }void topo(){ for(int i=1;i<=n;i++){ int s;//找到编号最小的、入度为 0 的节点 for(int j=1;j<=n;j++){ if(in[j]==0){ //找出前驱数量为零的的点 s=j; break; } }q.push(s);in[s]=-1;//将第一名的前驱数量设为-1 for(int j=1;j<=n;++j){ //将相关(前驱为 s)的点处理 if(mp[s][j]){ in[j]--;//入度减 1 mp[s][j]=0;//删掉这条边 } } }print();//输出 }int main(){ while(cin>>n>>m){ memset(in,0,sizeof(in));//初始化 memset(mp,0,sizeof(mp)); for(int i=0;i<m;i++){ int x,y;cin>>x>>y; if(mp[x][y]==0){ //避免重复的数据输入 mp[x][y]=1; // in[y]++;//前驱数量加一 } }topo(); }return 0; } ```

题目列表

  • 最大食物链计数
  • 摄像头
  • [USACO20FEB] Timeline G
  • 郁闷的记者
  • [USACO08JAN] Cow Contest S
  • 旅行计划
  • 游走
  • [NOIP 2003 提高组] 神经网络
  • 绿豆蛙的归宿
  • 排序
  • [SCOI2011] 糖果
  • [Wind Festival] Running In The Sky
  • [HNOI2015] 菜肴制作