【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;
}

 

posted @ 2017-09-12 14:40  GXZlegend  阅读(582)  评论(0编辑  收藏  举报