【bzoj4010】[HNOI2015]菜肴制作 拓扑排序+堆
题目描述
给你一张有向图,问:编号-位置序(即每个编号的位置对应的序列)最小(例如1优先出现在前面,1位置相同的2优先出现在前面,以此类推)的拓扑序是什么?
输入
第一行是一个正整数D,表示数据组数。
接下来是D组数据。 对于每组数据:
第一行两个用空格分开的正整数N和M,分别表示菜肴数目和制作顺序限制的条目数。
接下来M行,每行两个正整数x,y,表示“x号菜肴必须先于y号菜肴制作”的限制。(注意:M条限制中可能存在完全相同的限制)
输出
输出文件仅包含 D 行,每行 N 个整数,表示最优的菜肴制作顺序,或者”Impossible!”表示无解(不含引号)。
样例输入
3
5 4
5 4
5 3
4 2
3 2
3 3
1 2
2 3
3 1
5 2
5 2
4 3
样例输出
1 5 3 4 2
Impossible!
1 5 2 4 3
题解
拓扑排序+堆
正着直接按字典序最小拓扑排序是错的,反例:
按照题目描述,答案应该为1 5 2 3 4,而正着拓扑排序的答案为1 3 4 5 2
这里有一个神奇的结论:正的编号-位置序(每个编号的位置对应的序列)等于反的位置-编号序(每个位置的编号对应的序列,即字典序)的逆序。
换句话说,本题的答案就是把图反过来得到的图的字典序最大的拓扑序的逆序。
证明:反图的字典序最大,就是尽量的把编号较大的数放在反图遍历序的前面,对应的就在原图的后面,就相当于所有小于这个编号的数向前移动了1个位置。因此,对于某数都是尽量的在它的后面放数。因此尽量的让大数在反图中先遍历,得到的字典序最大拓扑序的逆序就是远答案。
所以使用堆来维护字典序,进行拓扑排序,倒过来输出即为答案。
#include <queue> #include <cstdio> #include <cstring> #define N 100010 using namespace std; priority_queue<int> q; int head[N] , to[N] , next[N] , cnt , d[N] , ans[N] , tot; inline void add(int x , int y) { to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt , d[y] ++ ; } int main() { int T; scanf("%d" , &T); while(T -- ) { memset(head , 0 , sizeof(head)) , memset(d , 0 , sizeof(d)) , cnt = tot = 0; int n , m , i , x , y; scanf("%d%d" , &n , &m); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &x , &y) , add(y , x); for(i = 1 ; i <= n ; i ++ ) if(!d[i]) q.push(i); while(!q.empty()) { x = q.top() , q.pop() , ans[++tot] = x; for(i = head[x] ; i ; i = next[i]) { d[to[i]] -- ; if(!d[to[i]]) q.push(to[i]); } } if(tot == n) for(i = n ; i ; i -- ) printf("%d " , ans[i]); else printf("Impossible!"); printf("\n"); } return 0; }