算法—-拓扑排序


前言

  这篇文章是我对于自己学习拓扑排序的一个总结。因为自己也在学习所以有错的地方也请大家见谅。


什么是拓扑排序

  这里我就简单的说明一下。拓扑排序就是在有向图中只有出的没有进的点先排除掉。你只要遇到这类的题只要记住这个思想接下来就是空间和时间复杂度的考虑了。(如果你要在深度了解或是看我这个说明不明白的话可以自行百度,百度百科链接

  在一个图中如果你要想排出所有的点,那么只有无环的有向图才可以把所有的点排出去。而且当我们进行拓扑排序的时候,由于大家想法不一样每一个人排出来的顺序都有可能不同。题目就会在这两个方面来进行设题。题目有时不一定会给你有向无环图,而且为了保证答案的唯一性题目会给你一个排序的规则。这时候你必须要看清题目的需求来进行做题。

如何实现拓扑排序

  首先我们要先建立有向图。那么我们肯定要有两个数据:一个表示点,另一个表示点的指向。在这里举一些本人常用的方法,当然具体使用的时候要按照实际的需求来。

方法一:二维数组。

用行来表示图上的点,用列表示点的指向。一开始我们用0来填充这个表示图的二维数组。假设点1指向点2那么我们就可以让a[1][2]=1用这样的方式来表示点1指向点2

方法二:用队列或者栈

  同样我们也要一个创建一个队列或者栈的数组。我用下标值来表示点。如果点1指向点2那么我们就向下标为1的数组中插入一个数字2来表示点1指向点2

  这个两个就是我现在经常使用的方法了。第一个方法的时间和空间复杂度都比较大,但是它的想法简单不易错。如果题目需要很大的空间,那么你就要用到第二个方法。

  建完图后,我们就进行拓扑了。这里我们必须再次谈到拓扑排序的中心思想了——在有向图中只有出的没有进的点先排除掉。那么我们必要先找出来没有进的点。如果我们单纯用图来查找,那么我们会发现这样比较困难。因为你很难表示一个点被排除后接下来满足条件的点怎么来排除和如何表示你这个点已经排出去了。这里我们可以用一个整型数组b来表示一个点有多少的进,用另一个整型数组c来表示一个点是否被排出去。我们同样用下标来表示点并一开始用0来填充数组bc。每次有一个点A指向点B那么我们就可以进行一个b[B]++的操作。每次点A被排出去了,那么就将数组c[A]设置为1。(当然你可以用布尔型来表示是否排出,只是我在用的时候经常会出错。所以我这里还是建议用一个整型数组来存比较好。那么我数组c之所以设0为未被排除是因为我在实际使用memset这个函数的时候设1会出问题。虽然我不知道为什么,但是我多次wa后知道设0比较好。)

举例:

  比如我们创建一个数组b来存放图中点的进的个数,那么这里我们还是点1指向点2

这时候我们就让b[2]++。这样**b[2]**就记录了有多少个点指向点2。

进行拓扑

  当一切准备就绪就只要从头遍历数组b当遇到b[i]==0,那么就表示i这个点只有出没有进。这时候我们判断c[i]是否为0。如果c[i]==1就意味着它已经被排除过了就不要再排除它 了。如果c[i]==0就可以把i这个点排除出去并把**c[i]**设为1。然后我们遍历二维数组a[i][]。这时候我们判断二维数组是否满足a[i][j]==1.如果满足则把b[j]--。接下来再重新从头遍历一遍直到再也排不了的时候。

  这里我用c++来演示一下拓扑排序。我这里演示的代码是小的先被排出。这里是输入n和m表示有n个点和接下来输入m行的x和y,用空格隔开表示点x指向点y。

输入:

5 4

1 5

2 5

5 4

5 3

演示代码:

(1)二维数组

#include<iostream>//二维数组的演示
#include<cstring>
using namespace std;
int main()
{
	int n, m, a[100][100], x, y,b[100],c[100];
	while (cin>>n>>m)
	{
		memset(a, 0, sizeof(a));
		memset(b, 0, sizeof(b));
		memset(c, 0, sizeof(c));
		//这里先进行初始化
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			a[x][y] = 1;//表示点x指向y
			b[y]++;//那每次指向y点,数组b都要++
		}
		for (int i = 0; i < n; i++)//我们要排除n个点那么就要循环n次
		{
			for (int j = 1; j <= n; j++)//对数组b进行遍历找到b[j]==0的点
			{
				if (b[j]==0&&c[j]==0)//那么如果c[j]==0那么表示j还没被排出去
				{
					cout << j << " ";
					c[j] = 1;
					for (int z = 1; z <= n; z++)
					{
						if (a[j][z]==1)
						{
							b[z]--;
							//因为j点被排出去了,那么j点所指向的点都要减1
						}
					}
					break;
				}
			}
		}
		cout << endl;
	}
	return 0;
}

(2)队列

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int main()
{
	queue<int> q[100], qt;
	int n, m, x, y, b[100], c[100];
	while (cin>>n>>m)
	{
		memset(b, 0, sizeof(b));
		memset(c, 0, sizeof(c));
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			q[x].push(y);
			b[y]++;
		}
		for (int i = 1; i <= n; i++)
		{
			if (b[i]==0&&c[i]==0)
			{
				c[i] = 1;
				qt.push(i);
				cout << i << " ";
				break;
			}
		}
		int t;
		while (!qt.empty())
		{
			t = qt.front();
			while (!q[t].empty())
			{
				b[q[t].front()]--;
				q[t].pop();
			}
			for (int i = 1; i <= n; i++)
			{
				if (b[i] == 0 && c[i] == 0)
				{
					c[i] = 1;
					qt.push(i);
					cout << i << " ";
					break;
				}
			}
			qt.pop();
		}
		cout << endl;
	}
	return 0;
}

其实无论是队列还是二维数组都是遵循着拓扑排序的中心思想——没有进只有出的点先被排出。

关于拓扑排序的题目

这里我直接讲实例。不过前面要照护到刚刚学习的人所以会讲的尽量细致因此字数也会多。


HDU - 2647 Reward

Problem Description

Dandelion’s uncle is a boss of a factory. As the spring festival is coming , he wants to distribute rewards to his workers. Now he has a trouble about how to distribute the rewards.
The workers will compare their rewards ,and some one may have demands of the distributing of rewards ,just like a’s reward should more than b’s.Dandelion’s unclue wants to fulfill all the demands, of course ,he wants to use the least money.Every work’s reward will be at least 888 , because it’s a lucky number.

Input

One line with two integers n and m ,stands for the number of works and the number of demands .(n<=10000,m<=20000)
then m lines ,each line contains two integers a and b ,stands for a’s reward should be more than b’s.

Output

For every case ,print the least money dandelion ‘s uncle needs to distribute .If it’s impossible to fulfill all the works’ demands ,print -1.

Sample Input

2 1
1 2
2 2
1 2
2 1

Sample Output

1777
-1

中文翻译:

问题描述

蒲公英的叔叔是一家工厂的老板。春节快到了,他想给工人们发奖金。现在他面临着如何分配奖金的难题。

工人们会比较他们的报酬,有些人可能会有分配报酬的要求,就像a的报酬应该比b的多。蒲公英的无知想要满足所有的需求,当然,他想用最少的钱。每个工作的奖励至少是888,因为这是一个幸运数字。

输入

其中一行有两个整数n和m,表示作品的数量和需求的数量。(n<=10000,m<=20000)

那么m行,每一行包含两个整数a和b,代表a的奖励应该比b的多。

输出

对于每种情况,打印蒲公英的叔叔需要分配的最少的钱。如果不可能满足所有的工作要求,打印-1。

(翻译使用有道翻译)

对于Reward题目的分析:

  首先我们要知道题目要我们干什么。从题意上看,就是给我们一堆数据让我们建立一个图来表示工厂工人之间的工资关系。最终输出工厂老板最少要给多少的工资或者老板不能满足所有需求输出一个负一。

  不管怎么样只要做拓扑排序那么我们必定是要先建图。这时候我们要看到题目最大要一万个工人和两万个需求。你要是用二维数组必然时间和空间复杂度都不允许。所以你只能用其他的方法。反正我用队列(很多人使用vertor这个头文件来做,但是我队列用习惯了。反正对于这题来说它们是一样的)。这里我们还要注意一件事情那就是老板不一定能满足所有工人的需求。这个也就是说题目的所建的图不一定是有向无环图。因此我们要用一个计数的变量count来表示一共排出了多少点。如果变量count等于工人数就意味着全部的点都排出去了,那我们就把我们计算出来的钱数输出出去否则就要输出负一。

  图建完后,并且我们也把有向无环图的判断搞定了。因为我们还需要计算老板要给多少钱,所以我们再用一个变量来存放他们多的钱数。那么最低级的工人肯定多的钱数是0.比他高一级的就是1。而且这个钱数必然要和所对应的点挂钩。所以你可以用一个新开一个数组来存放多的钱数。不过你要是去看其他人对这题的做法基本上都是用一个结构体来做这题(用结构体不容易错)。结构体的建立如下:

struct Node
{
	int num, value;//num表示点,value表示多的钱数
};

  最后就是输出。如果满足count等于工人数的条件,我们只要把所有的value相加并再加上工人数*888否则就输出负一。

AC代码展示

#include <iostream>
#include <queue>
using namespace std;
struct Node
{
	int num, value;//num表示点,value表示多的钱数
};
int main()
{
	int n, m, x, y;
	while (cin >> n >> m)
	{
		queue<int> q[10005];//建图用的,最好在循环里建。
		int a[10005];//用来记录是否被排除
		Node t, t1;
		queue<Node> qn;//保存已经被排除的点,并记录下它们对应的value
		memset(a, 0, sizeof(a));//初始化
		int count = 0;//用来记录被排除了多少个
		long long sum = 0;//用来记录总工资,因为工人数多所以我直接开long long
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			q[y].push(x);
			a[x]++;
		}//建图
		for (int i = 1; i <= n; i++)//我们一定要找到所有的一开始就可以被排出的点,以便于它们后一级的value更好计算。
		{
			if (a[i] == 0)//先找到可以被排的点
			{
				sum += 888;//工人的基本工资可以在这直接加,或者最后来一个工人数*888也是可以的。
				t.num = i;
				t.value = 0;//第一个被排的工人必然多钱数是0了。
				qn.push(t);
				count++;//记录被排了多少个点
			}
		}
		while (!qn.empty() && count <= n)//进行一个优化,显然当count==n时,所有的点就都找到了。
		{
			t1 = qn.front();
			qn.pop();
			while (!q[t1.num].empty())
			{
				int h = q[t1.num].front();
				a[h]--;
				q[t1.num].pop();
				if (a[h] == 0)
				{
					t.num = h;
					t.value = t1.value + 1;//后面被排出的点必然比他的前一级的value高1
					sum += 888 + t.value;
					count++;
					qn.push(t);
				}
			}
		}
		if (count != n)//判断是否count等于工人数
		{
			cout << "-1" << endl;
		}
		else
		{
			cout << sum << endl;
		}
	}
	return 0;
}

HDU- 3342 Legal or Not

Problem Description

ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that just like a big family. Every day,many “holy cows” like HH, hh, AC, ZT, lcc, BF, Qinz and so on chat on-line to exchange their ideas. When someone has questions, many warm-hearted cows like Lost will come to help. Then the one being helped will call Lost “master”, and Lost will have a nice “prentice”. By and by, there are many pairs of “master and prentice”. But then problem occurs: there are too many masters and too many prentices, how can we know whether it is legal or not?

We all know a master can have many prentices and a prentice may have a lot of masters too, it’s legal. Nevertheless,some cows are not so honest, they hold illegal relationship. Take HH and 3xian for instant, HH is 3xian’s master and, at the same time, 3xian is HH’s master,which is quite illegal! To avoid this,please help us to judge whether their relationship is legal or not.

Please note that the “master and prentice” relation is transitive. It means that if A is B’s master ans B is C’s master, then A is C’s master.

Input

The input consists of several test cases. For each case, the first line contains two integers, N (members to be tested) and M (relationships to be tested)(2 <= N, M <= 100). Then M lines follow, each contains a pair of (x, y) which means x is y’s master and y is x’s prentice. The input is terminated by N = 0.
TO MAKE IT SIMPLE, we give every one a number (0, 1, 2,…, N-1). We use their numbers instead of their names.

Output

For each test case, print in one line the judgement of the messy relationship.
If it is legal, output “YES”, otherwise “NO”.

Sample Input

3 2
0 1
1 2
2 2
0 1
1 0
0 0

Sample Output

YES
NO

Author

QiuQiu@NJFU

中文翻译:

问题描述

ACM-DIY是一个大型的QQ群,许多优秀的acm爱好者都聚集在这里。它是如此和谐,就像一个大家庭。每天,许多“牛”像HH, HH, AC, ZT, lcc, BF, Qinz等聊天在线交流他们的想法。当有人有问题的时候,很多像迷路一样的牛都会来帮忙。然后,被帮助的人会称迷失的“主人”,迷失将有一个很好的“普伦蒂斯”。不久,有许多对“师傅和徒弟”。但问题出现了,有太多的师傅和学徒,我们怎么知道它是否合法呢?

我们都知道师傅可以有很多学徒,学徒也可以有很多师傅,这是合法的。然而,有些奶牛并不诚实,它们有不合法的关系。以HH和3xian为例,HH是3xian的师傅,同时3xian是HH的师傅,这是违法的!为了避免这种情况,请帮助我们判断他们的关系是否合法。

请注意“师傅和徒弟”的关系是可传递的。这意味着如果A是B的主人,B是C的主人,那么A就是C的主人。

输入

输入由几个测试用例组成。对于每种情况,第一行包含两个整数,N(要测试的成员)和M(要测试的关系)(2 <= N, M <= 100)。然后是M行,每一行包含一对(x, y) x是y的主格,y是x的徒弟。输入被N = 0终止。

简单地说,我们给每个数一个数字(0、1、2、…)n - 1)。我们用他们的号码而不是他们的名字。

输出

对于每个测试用例,打印一行混乱关系的判断。

如果是合法的,输出“YES”,否则输出“NO”。

(翻译使用有道翻译)

对于Legal or Not题目的分析:

  首先我们要知道这是让我们判断一个关系是否正常。那么关系这东西一般就是图了。那么我们从题目中看到一个正常的关系就是不存在类似“A是B的师傅,B是C的师傅,C是A的师傅”这样的关系。我们用图的话来说就是这个图中不能存在环。然后你又要表明师傅和徒弟之间的关系,则这个图必是有向图。那我们分析到这里就很明了了,题目就是要我们判断它给我们的数据是否为有向无环图。

  如果一个如是有向的无环图,那它必然会被拓扑排序排除全部的点。那么也就是说只要排除点的数目count==n我们可以输出YES反之输出NO。我们再来看这题的数据,我们发现题目中的n最大才为100。这也意味着,这个题目我们可以使用二维数组。但是你们可能会想为什么不用我之前说的队列呢?第一数据不大,我们用二维数组比较直观。第二因为数据不大,所以题目中可能会出现重复数据。这个重复数据虽然不会对我们的建图产生影响,但是它影响你记录一个点的被指向数。

比如:数据0 1,0 1

你用数组b来记录一个点的被指向数。数组a来建图。

第一个0 1输入的时候,a[0][1]=1b[1]++

第二个0 1输入的时候你的**a[0][1]原本就是1了,再让它等于1又不会出问题。但是你的b[1]**就重复加了。

因此我们需要对这个进行判断,防止有重复的数据。这也是为什么很多人在数据大的时候用vector,因为数据大也有可能题目给你重复数据。而vector会比队列有这更好的函数去查找重复值。

AC代码展示

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int main()
{
	int a[101][101], n, m, x, y, v[101], b[101];
	while (cin>>n>>m&&n!=0)
	{
		memset(a, 0, sizeof(a));
		memset(b, 0, sizeof(b));
		memset(v, 0, sizeof(v));
		//初始化
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			if (a[x][y]==0)//防止重复数据使得b[y]重复加
			{
				a[x][y] = 1;//表示x是y的师傅
				b[y]++;
			}
		}
		queue<int> q;
		int count = 0;
		for (int i = 0; i < n; i++)//找到最强的师傅,即只有出没有进的点
		{
			if (b[i]==0&&v[i]==0)
			{
				q.push(i);
				v[i] = 1;//设其为一表示已经排除
				count++;//累加已排出的点
			}
		}
		while (!q.empty()&&count<=n)
		{
			int t = q.front();
			q.pop();
			for (int i = 0; i < n; i++)
			{
				if (a[t][i]==1)
				{
					b[i]--;
					if (b[i]==0&&v[i]==0)
					{
						q.push(i);
						count++;
						v[i] = 1;
					}
				}
			}
		}
		if (count==n)//如果是一个正常的关系那么必定所有的点都被排除出去
		{
			cout << "YES" << endl;
		}
		else
		{
			cout << "NO" << endl;
		}
	}
	return 0;
}

HDU - 4857 逃生

Problem Description

糟糕的事情发生啦,现在大家都忙着逃命。但是逃命的通道很窄,大家只能排成一行。

现在有n个人,从1标号到n。同时有一些奇怪的约束条件,每个都形如:a必须在b之前。
同时,社会是不平等的,这些人有的穷有的富。1号最富,2号第二富,以此类推。有钱人就贿赂负责人,所以他们有一些好处。

负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。

那么你就要安排大家的顺序。我们保证一定有解。

Input

第一行一个整数T(1 <= T <= 5),表示测试数据的个数。
然后对于每个测试数据,第一行有两个整数n(1 <= n <= 30000)和m(1 <= m <= 100000),分别表示人数和约束的个数。

然后m行,每行两个整数a和b,表示有一个约束a号必须在b号之前。a和b必然不同。

Output

对每个测试数据,输出一行排队的顺序,用空格隔开。

Sample Input

1
5 10
3 5
1 4
2 5
1 2
3 4
1 4
2 3
1 5
3 5
1 2

Sample Output

1 2 3 4 5

Author

CLJ

对于此题——“逃生”的题目分析

  这题目要是你不认真去分析,你可能就会想拓扑排序加降序输出。但是这题题目设坑就设在这句话“要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。”这句话就是告诉我们,这个不是简单的拓扑+降序输出。我一开始也没有想到直到看到这位大哥的解说(链接),我才AC的。

  如果你们没有想去看那位大哥的解说。那就由我再来给你们分析一下吧。为了满足题目的要求,那么我们必须是反向建图+升序队列反向输出。这是因为题目要求你尽量让1在前面。你可能会想那我直接降序输出不就好了。这里我把那位大哥给的数据展示一下:

1

6 4

6 3

3 1

5 4

4 2

如果你就按照降序输出来输出的就是5 4 2 6 3 1。按照题目的要求,你要尽量使1在2的前面。这组输出明显不符合题意。那符合题意的数据应该是6 3 1 5 4 2。你看这两个数据是不是就是让5 4 2和6 3 1这两个块互换。那我们分析一下,数据所建出来的图应该是6>3>1和5>4>2。按照拓扑排序来说我们可以先排出6或5。如果我们先排出6,那么排完后我们又可以排除3或是5。以此类推。你们这时候或许有一个疑惑,为什么我们不先排出5呢?我们首先是要尽量让1排在2的前面。那也就是说,我们要先排除1。你若要先排除1就要把1之前的先排除掉之后再考虑其他的。那为什么我们要反向建图呢?按照之前的分析,我们必先要先把1之前的点都输出后再排除剩下点中的最小值之前的点都排掉。你可以想一下你用反向建图的时候,当你的qt为1的时候,按照拓扑排序的代码你必然是把1之后可以排除的点都排出去也就是说比1早走的点都走了。因为我们使用了反向建图导致它的输出是反着的,所以我们的输出也必须反着来。那为什么我们又要用一个升序的优先队列qt来预先存放排除点呢?那么你想我们用反向输出,那么是不是1越晚进入qt也就意味着它越早输出1之前的点也越早输出。

  我说了这么多可能有点乱因为这题的确有点难度。如果你还不明白的话可以去看那位大哥的(链接)。如果还是不明白的话,你还是自己多想想吧。

  接下来就是和上面的分析差不多了。建图拓扑就结束了。

AC代码展示:

#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<cstdio>
#include<stack>
using namespace std;
int main()
{
	int T, n, m, a, b;
	while (~scanf("%d", &T))
	{
		while (T--)
		{
			scanf("%d %d", &n, &m);
			queue<int> q[30005];
			int v[30005], c[30005];
			memset(c, 0, sizeof(c));
			memset(v, 0, sizeof(v));
			//初始化
			for (int i = 0; i < m; i++)
			{
				scanf("%d %d", &a, &b);
				q[b].push(a);//反向建图
				c[a]++;
			}
			priority_queue<int> qt;//设置降序优先队列
			stack<int> os;
			for (int i = 1; i <= n; i++)
			{
				if (c[i] == 0)
				{
					qt.push(i);
					v[i] = 1;
				}
			}
			while (!qt.empty())
			{
				int t = qt.top();
				qt.pop();
				os.push(t);//用栈存先进后出
				while (!q[t].empty())
				{
					int t1 = q[t].front();
					q[t].pop();
					c[t1]--;
					if (c[t1] == 0 && v[t1] == 0)
					{
						qt.push(t1);
						v[t1] = 1;
					}
				}
			}
			bool f = false;
			while (!os.empty())
			{
				if (f)
				{
					cout << " ";
				}
				cout << os.top();
				os.pop();
				f = true;
			}
			cout << endl;
		}
	}
	return 0;
}

HDU - 5695 Gym Class

Problem Description

众所周知,度度熊喜欢各类体育活动。

今天,它终于当上了梦寐以求的体育课老师。第一次课上,它发现一个有趣的事情。在上课之前,所有同学要排成一列, 假设最开始每个人有一个唯一的ID,从1到N,在排好队之后,每个同学会找出包括自己在内的前方所有同学的最小ID,作为自己评价这堂课的分数。麻烦的是,有一些同学不希望某个(些)同学排在他(她)前面,在满足这个前提的情况下,新晋体育课老师——度度熊,希望最后的排队结果可以使得所有同学的评价分数和最大。

Input

第一行一个整数T,表示T(1≤T≤30) 组数据。

对于每组数据,第一行输入两个整数N和M(1≤N≤100000,0≤M≤100000),分别表示总人数和某些同学的偏好。

接下来M行,每行两个整数A 和B(1≤A,B≤N),表示ID为A的同学不希望ID为B的同学排在他(她)之前。你可以认为题目保证至少有一种排列方法是符合所有要求的。

Output

对于每组数据,输出最大分数 。

Sample Input

3
1 0
2 1
1 2
3 1
3 1

Sample Output

1
2
6

Source

2016"百度之星" - 初赛(Astar Round2A)

对于此题——“Gym Class”的题目分析

  题意:题目就是要你排一个队伍,使得队伍中所有人评分相加的值的最大。队伍中评分计算是按照这个人和这个人前面的人中评分最低作为这个人的分数。

例子:

队伍排序为:3 2 1。

对于3来说,它前面没有人,那么自然评分就是它自己。

对于2来说,它的前面有3。但是它自己的序号是2比3小。所以按照评分的规则它的评分只能是2。

对于1来说,它的前面有3和2。但是它的序号是在1,2,3中是最小的。所以按照评分的规则它的评分只能是2。

  故队伍的总评分为3+2+1=6。这个也是题目给的案例中的最后一例所建的队伍。

   题意我已经讲明了,但是这个题目到底应该怎么做呢?

  我们再看看评分的规则:每个同学会找出包括自己在内的前方所有同学的最小ID。而我们需要做出来的是最大的总评分,也就是只要每一个同学得到最大的评分那么我们自然就可以得到最大的总评分。那么为了要每一个同学都得到最大的总评分,则他们的评分最好就是自己。那为了得到这样的结果只要前面的数比它自身大就好了。这就需要我们尽可能把大的数排在前面。这时候你就需要来一个大数优先队列来排。

  可能你现在还是有点不明白为什么我们要选择大数优先队列来存储。这里我举个例子看你能不能理解(如果你已经理解了,那你可以直接去看AC代码展示了)。

例子:

1

3 1

3 1

第一行表示有几个队伍。

第二行第一个数字表示有3个人,第二个数字表示只有一个关系。

第三行表示3要在1的前面。

那么你已经知道1和3的排列。但是你还要想一下2应该放在那里。

那么会有以下的排列可能:

2 3 1 总评分为5

3 2 1 总评分为6

3 1 2 总评分为5

那么你会发现只有2在3后面的时候总评才会最大。也就是说在满足条件的情况下只有让越大的数放在越前面则总评越大。因为这样对应的数才越可能是它自己。

  解题做法:拓扑排序+大数优先队列。

AC代码展示:

#include<iostream>
#include<queue>
#include<climits>
#include<cstring>
#include<cstdio>
using namespace std;
queue<int> q[100010];//建图表示的点
int a[100010];//记录点的进入值
int main()
{
	int n, m, T, x, y, minn, count;
	long long sum;
	while (cin >> T)
	{
		while (T--)
		{
			scanf("%d %d", &n, &m);
			for (int i = 0; i <= n; i++)
			{
				a[i] = 0;
			}//初始化
			for (int i = 0; i < m; i++)
			{
				scanf("%d %d", &x, &y);
				q[x].push(y);
				a[y]++;
			}//建图
			priority_queue<int> Q;//大数优先队列
			for (int i = 1; i <= n; i++)
			{
				if (a[i] == 0)
				{
					Q.push(i);
				}
			}
			minn = Q.top();//第一个数的的评分必是它自己
			sum = 0;
			count = 0;
			while (!Q.empty())
			{
				if (count == n)
				{
					break;
				}//防止死循环,主要是我交G++的时候一直MLO不过我用c++交就过了
				int t = Q.top();
				Q.pop();
				if (minn > t)
				{
					minn = t;
				}//更新最小值
				sum = sum + minn;
				while (!q[t].empty())
				{
					int t1 = q[t].front();
					q[t].pop();
					a[t1]--;
					if (a[t1] == 0)
					{
						Q.push(t1);
					}
				}
				count++;
			}
			cout << sum << endl;
		}
	}
	return 0;
}

HDU - 1285

Problem Description

有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。

Input

输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。

Output

给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

Sample Input

4 3
1 2
2 3
4 3

Sample Output

1 2 4 3

Author

SmallBeer(CML)

Source

杭电ACM集训队训练赛(VII)

Recommend

lcy

对于此题——“确定比赛名次”的题目分析

  这一题并不是特别的难,你只要注意好题目的坑然后用拓扑排序就可以了。

  本题的题目坑在于输出,前面都是简单的建图就结束了。这题难点就在输出上。输出的第一个条件是“最后一名后面没有空格”。第二个条件是“要求输出时编号小的队伍在前”。这条件也就是说你的输出最后要没有空格且尽量小号在前面。所有这题就要用到小数优先队列加上拓扑排序。

  当然我说过这题并不是特别难,你也可以从头开始遍历找到满足需求的点输出并将它所连的线排除后再从头开始遍历做上面的步骤直到所有的点排出。代码展示的就是不用优先队列的做法。不过优先队列的想法会更加简单。

AC代码展示

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
	int a[505][505], b[505];//a为建图用点,b为记录点的进数
	int n, m, x, y;
	while (cin >> n >> m)
	{
		memset(a, 0, sizeof(a));
		memset(b, 0, sizeof(b));//初始化
		while (m--)
		{
			cin >> x >> y;
			if (a[x - 1][y - 1] == 0)//防止多余的数据
			{
				a[x - 1][y - 1] = 1;
				b[y - 1]++;
			}
		}
		bool f = false;
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (b[j] == 0)
				{
					if (f)
					{
						cout << " ";
					}
					cout << j + 1;
					for (int h = 0; h < n; h++)
					{
						if (a[j][h] == 1)
						{
							b[h]--;
						}
					}
					b[j] = -1;
					f = true;
					break;
				}
			}
		}
		cout << endl;
	}
	return 0;
}

  希望我讲解能为你带来一点作用。如果有兴趣的话,可以查看一下我的个人博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值