HDU 4857 (反向拓扑排序 + 优先队列)

题意:有N个人,M个优先级a,b表示a优先于b,并且每个人有个编号的优先级,输出顺序。

思路来自:与PKU3687一样

在基本的拓扑排序的基础上又增加了一个要求:编号最小的节点要尽量排在前面;在满足上一个条件的基础上,编号第二小的节点要尽量排在前面;在满足前两个条件的基础上,编号第三小的节点要尽量排在前面……依此类推。(注意,这和字典序是两回事,不可以混淆。)

如图 1 所示,满足要求的拓扑序应该是:6 4 1 3 9 2 5 7 8 0。

图 1 一个拓扑排序的例子

一般来说,在一个有向无环图中,用 BFS 进行拓扑排序是比较常见的做法,如算法 1 所示。但是它不一定能得到本题要求的拓扑序。

1. 把所有入度为 0 的节点放进队列 Q

2. WHILE: Q 不是空队列

3.     从 Q 中取出队列首元素 a,把 a 添加到答案的尾部

4.     FOR:所有从 a 出发的边 a → b

5.         把 b 的入度减 1。如果 b 的入度变为 0,则把 b 放进队列 Q。

算法 1 用 BFS 进行拓扑排序

为了解决本问题,下面让我来探究一下拓扑序的一些性质。以图 1 为例,节点 0 毫无疑问排在最后。除了节点 0 以外,有三条互相平行的路径:6 → 4 → 1、 3 → 9 → 2 和 5 → 7 → 8。一条路径上的各个节点的先后关系都是不能改变的,比如路径 6 → 4 → 1 上的三个节点在拓扑序中,一定是 6 在最前,1 在最后。但是,互相平行的各条路径,在总的拓扑序中任意交错都是合法的。比如,以下都是图 1 的合法拓扑序:

6 4 1 3 9 2 5 7 8 0、 3 6 9 4 5 1 7 8 2 0、 5 6 4 7 3 8 1 9 2 0、 3 5 6 4 1 7 9 2 8 0、 6 5 7 8 4 3 9 2 1 0。

怎么才能找出题目要求的拓扑序呢?在这里,我想用字典序最先的拓扑序来引出这个算法。算法 2 可以求出字典序最先的拓扑序。

1. 把所有入度为 0 的节点放进优先队列 PQ

2. WHILE: PQ 不是空队列

3.     从 PQ 中取出编号最小的元素 a,把 a 添加到答案的尾部

4.     FOR:所有从 a 出发的边 a → b

5.        把 b 的入度减 1。如果 b 的入度变为 0,则把 b 放进优先队列PQ。

算法 2 求出字典序最先的拓扑序

可见,算法 2 和算法 1 基本一样,只是把队列改成了优先队列。用它求出的图 1 的字典序最先的拓扑序为:3 5 6 4 1 7 8 9 2 0。但是这显然不是本题要求的答案,因为节点 1 的位置还不够靠前。

算法 2 可以算是一个贪心算法,每一步都找编号最小的节点。但是对于图 1 中的三条路径,头的编号比较小的,不一定要先出队列。正确的步骤应该如下:

  1. 节点 0 的位置是铁定在最后的,不用考虑。只考虑剩下的三条路径。
  2. 先找编号最小的,节点 1。把它和它所在的路径中位于它前面的节点全部拿出来。目前的答案是 6 4 1,这样, 节点 1 就尽量靠前了。
  3. 再找剩下的节点中编号最小的,节点 2。把它和它所在的路径中位于它前面的节点全部拿出来。目前的答案是 6 4 1 3 9 2 ,这样,节点 2 就尽量靠前了。
  4. 只剩下一条路径了,只能依次把其中的节点拿出来。最后答案就是 6 4 1 3 9 2 5 7 8 0。

显然,算法 2 的贪心策略对于这个问题是不可行的。不能着眼于每条路径的头,而是要找编号最小的节点在哪条路径上,优先把这条路径拿出来。但问题在于,在 BFS 的过程中,我们只能看到每条路径的头,看不到后面的节点,这该怎么办呢?

让我们换个角度想一想,节点 3 和 6,应该是 6 先出队列,因为节点 1 在 6 的后面。这和节点 3 和 6 的编号大小没有任何关系。但是,再看另外两条路径的尾部,节点 2 和 8,可以肯定地说,2 一定先出队列,因为它们后面都没有别的节点了,这个时候完全以这两个节点本身的编号大小决定顺序。归纳起来就是说,对于若干条平行的路径,小的头部不一定排在前面,但是大的尾部一定排在后面。于是,就有了算法 3。

1. 把所有出度为 0 的节点放进优先队列 PQ

2. WHILE: PQ 不是空队列

3.     从 PQ 中取出编号最大的元素 a,把 a 添加到答案的头部。

4.     FOR:所有指向 a 的边 b → a

5.        把 b 的出度减 1。如果 b 的出度变为 0,则把 b 放进优先队列PQ。

算法 3 求出本题目要求的拓扑序

#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<map>
#include<cmath>
#include<iostream>
#include <queue>
#include <stack>
#include<algorithm>
#include<set>
using namespace std;
#define INF 1e8
#define eps 1e-8
#define LL long long
#define maxn 505
#define mod  1000000007
vector<int>G[30003];
int out[30003];
int n,m,a[30003];
void toposort()
{
	priority_queue<int>q;
	int len=n;
	for(int i=1;i<=n;i++)
		if(out[i]==0)
			q.push(i);
	while(!q.empty())
	{
		int tmp=q.top();
		a[len--]=tmp;
		q.pop();
		for(int i=0;i<G[tmp].size();i++)
		{
			out[G[tmp][i]]--;
			if(out[G[tmp][i]]==0)
				q.push(G[tmp][i]);
		}
	}
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]);
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		int a,b;
		for(int i=1;i<=n;i++)
			out[i]=0,G[i].clear();
		while(m--)
		{
			scanf("%d%d",&a,&b);
			G[b].push_back(a);
			out[a]++;
		}
		toposort();
	}
	return 0;
}
/*
1
5 10
3 5
1 4
2 5
1 2
3 4
1 4
2 3
1 5
3 5
1 2
*/

HDU 4857 (反向拓扑排序 + 优先队列)

时间: 2024-08-10 13:38:34

HDU 4857 (反向拓扑排序 + 优先队列)的相关文章

hdu 4857 逃生 拓扑排序+优先队列,逆向处理

hdu4857 逃生 题目是求拓扑排序,但不是按照字典序最小输出,而是要使较小的数排在最前面. 一开始的错误思路:给每个点确定一个优先级(该点所能到达的最小的点),然后用拓扑排序+优先对列正向处理,正向输出.这是错误的,如下样例: 1 5 4 5 2 4 3 2 1 3 1 正确的解法:是反向建边,点大的优先级高,用拓扑排序+优先队列,逆向输出序列即可. 根据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定).本题要求较小的点排到前面,则可确定序列. (1)如果点

hdu 4857 逃生 拓扑排序+PQ,剥层分析

pid=4857">hdu4857 逃生 题目是求拓扑排序,但不是依照字典序最小输出,而是要使较小的数排在最前面. 一開始的错误思路:给每一个点确定一个优先级(该点所能到达的最小的点).然后用拓扑排序+优先对列正向处理,正向输出.这是错误的.例如以下例子: 1 5 4 5 2 4 3 2 1 3 1 正确的解法:是反向建边.点大的优先级高,用拓扑排序+优先队列,逆向输出序列就可以. 依据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定).本题要求较小的点排到

HDU 4857 逃生 拓扑排序好题 第一次做CLJ出的题

逃生 Problem Description 糟糕的事情发生啦,现在大家都忙着逃命.但是逃命的通道很窄,大家只能排成一行. 现在有n个人,从1标号到n.同时有一些奇怪的约束条件,每个都形如:a必须在b之前.同时,社会是不平等的,这些人有的穷有的富.1号最富,2号第二富,以此类推.有钱人就贿赂负责人,所以他们有一些好处. 负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推. 那么你就要安排大

HDU 4857 逃生(反向拓扑排序+优先队列)

( ̄▽ ̄)" //这题对序号输出有要求,较小的序号优先输出,所以用到优先队列 //优先队列是优先弹出值最大的,所以最后要反向输出结果,才是正确的output #include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<string> #include<cstdlib> #inc

hdu 4857 反向拓扑问题

尤其要注意拓扑的分层问题 不难理解 就是不怎么好想到 拓扑的思路这里就不累述了 #include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <queue> #define maxn 30007 using namespace std; int T; int n,m; int in[maxn]; vector<int> edge[

hdu 4857 逃生 拓扑排序+逆向建图

逃生 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Problem Description 糟糕的事情发生啦,现在大家都忙着逃命.但是逃命的通道很窄,大家只能排成一行. 现在有n个人,从1标号到n.同时有一些奇怪的约束条件,每个都形如:a必须在b之前.同时,社会是不平等的,这些人有的穷有的富.1号最富,2号第二富,以此类推.有钱人就贿赂负责人,所以他们有一些好处. 负责人现在

HDU 4857 拓扑排序 优先队列

n个数,已经有大小关系,现给m个约束,规定a在b之前,剩下的数要尽可能往前移.输出序列 大小关系显然使用拓扑结构,关键在于n个数本身就有大小关系,那么考虑反向建图,优先选择值最大的入度为零的点,这样得到的序列就是从大到小的,最后倒序输出就行了. 写这题的时候头好痛阿肚子好痛阿,再也不想熬夜了,一点效率都没有. /** @Date : 2017-09-29 19:29:12 * @FileName: HDU 4857 拓扑排序 + 优先队列.cpp * @Platform: Windows * @

hdu--1285 &amp;&amp; 4857 --正向 || 逆向拓扑排序 &amp;&amp; 优先队列

头太晕了 喝了太多 .. 就想提一点 对于 拓扑排序的这2题 为什么一个是正向 一个是逆向 主要是看题目要求  因为拓扑排序的结果总是有很多种存在的 一般来说 它会让你输出它指定要求的形式的答案 那么 如果是按字典序输出 就是 greater<int> 情况下的优先队列 并且 正向 如果是尽量使小的数字 靠前输出 而不是追求 字典序 可以考虑 逆向拓扑 逆向输出 但 这些都不是唯一的 一定要分析好题目 曾经 看过一个讲动态规划的word  说拓扑是为DP作准备的 似乎有点道理 这两题 代码 实

hdoj 4857 逃生(逆向拓扑排序+优先队列)

逃生 点击打开链接 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2161    Accepted Submission(s): 608 Problem Description 糟糕的事情发生啦,现在大家都忙着逃命.但是逃命的通道很窄,大家只能排成一行. 现在有n个人,从1标号到n.同时有一些奇怪的约束条件,每个都形如:a必须在b之前