P3243 [HNOI2015]菜肴制作
萌新今天刚刚接触拓扑排序,找到了一些题,然后就来做这道题紫题了(雾)
还是不要被题目的难度吓到吧,其实这道题挺模板的,思路感觉也比较好想,但是关于证明这些,我太菜,并不会。是一道非常好的入手拓扑排序的题,不像其他题目那样难想
对于一个限制 <i,j>,其实表示的就是 i 必须在 j 之前完成,我们把这个条件转化成图的形式就是 i 到 j 有一条有向边,然后就可以试着往拓扑排序的方向思考了
拓扑排序最基础的应用是什么,可以把它当做学习一个东西(比如语文吧),你要学习一篇文章,你总得先学会认字吧,学会认字肯定就要学会拼音,那么你要提前学习的东西就是前置知识,就相当于以上提到的 j (你要完成 j ,你必须先学会 i )。那这么一看,裸裸的拓扑排序
但是有一点变化的是什么呢?题目中的编号越小,表示它的预估质量越高(显而易见,这里指的不是字典序),所以对于编号小的,我们希望它在前面,而编号的就往后面放
因为如果正序按题目建边的话,假如1号点的入度不为1,而2的入度为0,我们应该先把限制1的点全部删掉,再把1弄出来,再去删2,这样才能保证最优,但是正序直接建立的话我们会把2直接删去
所以我们应该倒序建立。我们可以通过大根堆来实现把编号越大的越先处理,然后再倒序输出答案就可以了(可以理解为先把价值低的存储下来,价值高的就在后面,这个时候倒序输出就可以保证价值越高的越先做)
然后剩下了一个 Impossible ,这个也很简单,拓扑排序的另外一种应用,判断是否有环存在就可以了
#include<bits/stdc++.h>
using namespace std;
const int MAXN=500000;
int d;
int n,m;
int head[MAXN],tot;
struct node{
int net,to;
}e[MAXN];
void add(int x,int y){
e[++tot].to=y;
e[tot].net=head[x];
head[x]=tot;
} //链式前向星存储边,邻接矩阵一般来说会慢一点
int ru[MAXN]; //入度
int ans[MAXN],now; //存储答案
bool toop(){ //直接写成bool类型容易判断一些
priority_queue<int>q; //堆(优先队列) STL确实很方便
for(register int i=1;i<=n;i++){
if(ru[i]==0) q.push(i); //入度为0的加入堆
}
while(!q.empty()){
int x=q.top();
q.pop();
ans[++now]=x; //存储答案
for(register int i=head[x];i;i=e[i].net){
ru[e[i].to]--; //这个点删去了,它能到的点的入度就要--
if(ru[e[i].to]==0){ //入度为0的加入堆中
q.push(e[i].to);
}
}
}
if(now==n) return true; //如果刚好处理了n个,说明没有环
return false; //否则有环,出错
}
int main(){
scanf("%d",&d);
while(d--){
now=0,tot=0;
for(register int i=1;i<=n;i++) ru[i]=0,ans[i]=0;
memset(e,0,sizeof e);
memset(head,0,sizeof head); //注意清空数组,建议大家自己如果能手动清空就别用memset
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
add(y,x);
ru[x]++; //灵魂的反向建边
}
//bool k=toop();
//这个地方挺玄学的吧,我最开始声明了一个新变量判断是否有环(如上)
//然后它就超时了,所以这里直接写成toop()直接判断就可以了
if(toop()==false) puts("Impossible!"); //如果有环就不可能
else{
for(register int i=now;i>=1;i--) printf("%d ",ans[i]); //倒序输出答案
puts("");
}
}
return 0;
}
关于拓扑排序的一些具体介绍,大家可以去看看 洛谷日报 快速入手拓扑排序 (这不会被算作抄袭吧) ,然后宣传一下自己和同桌的博客