HDU 4857 逃生 (逆向拓扑排序、优先队列)

传送门:HDU 4857



题目给的输入输出数据不够典型,下面给出我自己的数据:


Sample Input

17 6 6 1 5 2 4 3 1 7 2 7 3 7   Sample Output

6 1 5 2 4 3 7

题目大意:

中文题,就不解释了。有题意可以知道,可能存在多种结果,答案需要保证数值小的尽可能排到前面。注意这和字典序最小不一样!



前置技能:

1. 拓扑排序:对于有向无环图来说,必有拓扑排序。具体做法是找到一个入度为0的点,输出并将与其相连的边删除,与其相连的点的入度-1,重复以上过程直到全部输出。可以发现,如果某次操作时入度为0的点的个数不唯一,则结果也不唯一。


2. 优先队列,和普通队列类似,普通队列是先进先出,优先队列是优先级高的先出。可以设置是数值大的优先级高还是数值小的优先级高,具体使用还请自行百度。



思路:

一开始理所当然的以为数值最小的尽可能排到前面就是字典序最小,其实不是的。大家可以对照着上面我给出的数据画下图。如果每次都是输出数值最小的入度为0的点,则结果是4 3 5 2 6 1 7,很明显1和2的位置在3的后面,不满足数值小的尽量排在前面的要求。


我们发现,每一次都取入度为0的并且最小的点输出不能使得最小的点排在尽可能靠前的位置,也就是局部最优不能使得全局最优。


所以可以考虑从后往前思考。我们发现,最后一个输出的一定是出度为0的数。相同时间如果存在多个出度为0的数,则数值大的一定是后输出的。借用一句别人总结的话小的头部不一定排在前面,但是大的尾部一定排在后面。



具体实现:

既然是从后面开始思考,那就逆向存储两个点的关系。这里我用的是vector数组存储。为了不导致超时,只在第一次的时候把入度为0的点入优先队列。然后每次保存优先队列中值最大的数,并把与其相连的点的入度-1,并判断该点入度是否为0,是则入队列。这样会减少重复运算。最后逆向输出保存的结果。



注意:

题目中输入的边可能有重复,但是不会影响到判断点的入度是否为0,因为只有把所有的重边删去后入度才可能为0.


代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector> //vector向量容器的头文件 
#include<queue> //优先队列的头文件 
using namespace std;

vector<int> map[100010]; //map[i]的第j个存储节点i的第j个相连节点 
int n,m,dgr[30010],ans[30010]; //dgr存储每个点的入度 

void solve()
{
	int i,now,cnt=n;
	priority_queue<int> Q;
	for(i=1;i<=n;i++) //将入度为0的点入优先队列 
		if(dgr[i]==0)
			Q.push(i);
	while(!Q.empty())
	{ //每次获取值最大的点,保存并删除与其相连的边 
		now=Q.top();
		Q.pop();
		ans[cnt--]=now;
		for(i=0;i<map[now].size();i++)
		{
			dgr[map[now][i]]--;
			if(dgr[map[now][i]]==0) //如果与now节点相连的点的入度为0则入队列 
				Q.push(map[now][i]);
		}
	}
	printf("%d",ans[1]); //逆向输出结果 
	for(i=2;i<=n;i++) printf(" %d",ans[i]);
	printf("\n");
}

int main()
{
	int i,t,u,v;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		memset(dgr,0,sizeof(dgr));
		for(i=1;i<=n;i++) //每次清空,不然会WA 
			map[i].clear();
		for(i=0;i<m;i++)
		{
			scanf("%d%d",&u,&v);
			map[v].push_back(u); //逆向存储节点信息 
			dgr[u]++;
		}
		solve();
	}
	return 0;
}

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/zuzhiang/article/details/77750889
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞