[NBOJ0014][PKU3687][Labeling Balls]

 

[题目要求]

http://n.boj.me/onlinejudge/newoj/showProblem/show_problem.php?problem_id=14

http://poj.org/problem?id=3687

[题目涉及的相关理论与算法]
1,图的拓扑排序以及演变。
2,贪心算法或者采用优先队列。

[题目中需要注意的地方]
1,标号问题,一定要仔细阅读题目,最后输出的是“按照标号顺序的重量列表”,我们拓扑排序的结果是“按照重量顺序的标号列表”。要转换一下。
2,可能会重复输入一个次序。。。我们必须在每次读入一个先后次序的时候,检测是否已经读入过,因为重复累计节点的出度会导致结果混乱。这个很容易忽略。。
3,注意输出结果的顺序不是字典序(他们都是拓扑出来的合理结果),字典序是“相同位置优先”,这里则是从标号最小的开始,越靠前越优先,比如拓扑的结果(注意不是最后的序列):4132比3241更符合题目要求,因为1更靠前,但是从字典序的角度却相反。

[思路过程]
    这个题花了挺长时间的。。但是并不沮丧。。看到网上有的两个小时就解决了,毕竟人家是做很多的。。我才刚刚起步嘛,很多东西都是遇到就学的。。所以学习新东西占了时间是最多的,调试什么的说白了也就半天就差不多了,最一开始还不知道拓扑排序,想成简单的插入排序了。。把给定的先后顺序直接相互插在新的链表里,写了半天总是WA,然后恍然大悟想到 比如 1-3,4-3,可以简单的插成1-4-3,但是如果后面再来一个4-1,问题就来了。。就会认为是出现环了,而显然4-1-3就是正确的,另外,它即使排成了也是字典序(那时还没意识到字典序和题目中的顺序的区别。。)
    后来查了一下,知道了拓扑排序,然后顺其自然写了一个,正向拓扑排序,但是拓扑排序的结果是很多的,按照对题目的理解,正向采用贪心,结果还是WA,网上查了查,看到了同样的问题。。哦,这时意识到了这时字典序。。不是题目中的要求,然后看到了可以使用反向字典序(就是考虑出度而不是入度),得出的结果就是题目要求的拓扑序。
    本以为结果很显然了,但是还是WA,快疯了。。。想到查一查PKU的discuss,哎。。怎么才想到要看呢。。,看到了一组神奇的数据,才终于明白前面说的第一个注意的地方。。

6 8
1 2
1 3
1 4
3 2
3 5
4 5
6 4
6 5

输出的结果是:
1 3 2 6 4 5
还是
1 3 2 5 6 4
呢?

我开始想题目中有4 5,显然第二个是不对的。。结果找了一个AC的程序才发现答案是第二个。。然后终于顿悟。。然后升华=。=
    好了,后面就不多说了,上代码了。。我没用拓扑排序中的入栈出栈,而是逆向贪心。。因为觉得这个顺其自然就理解了哈哈,存储形式是邻接表,邻接表中记录边用的是list容器,,刚学的容器……正好习惯一下~

[代码]

#include<iostream>
#include<list>
#include<cstring>			//这个包含memset函数。
#include<algorithm>			//后面有判重边时需要查找list中是否含有此边,用到了find方法。
using namespace std;

int const MAX = 201;
int const NONE = -1;		//拓扑排序中“删除或者访问过的节点标记”

struct itemNode{
	int outdegree;
	list<int> adNode;
};					//由于是逆向建图,所以讨论用的是出度,这个当然都是相对的	

void caseSolve(int m)		//子函数,打印出要求的序列
{
	itemNode NodeList[MAX];
	int sort[MAX];			//这个记录的是最后的结果

	int result[MAX];		//这个记录的是按照拓扑之后的序号。。。最后就是这里差点出现问题
	
	memset(result,0,sizeof(result));
	int numM=1,step=0;		//numM用来记录拓扑结果的序列。step是一个变量用来比对以判断
					//是否存在环(也就是说有环之后必然有节点访问不到就跳出了)
	for(int i=1;i<m+1;i++)	//初始化,每个出度设为0
	{
		NodeList[i].outdegree=0;
	}
	int numCase;
	cin>>numCase;	
	int numL,numR;			//numCase记录边的个数(包含重边),numL,numR就是输入的前后顺序啦
	while((numCase--) != 0)
	{
		cin>>numL>>numR;
		if(find(NodeList[numR].adNode.begin(),NodeList[numR].adNode.end(),numL) == NodeList[numR].adNode.end())
		{	//if里面是看是否出现了重边,也就是说numR节点的list中记录的是指向此点的来源标号。如果
			//如果来源里已经有了numL,那么就是重边了,就不进行下面的统计了
			NodeList[numL].outdegree ++;	//不是重边,那么出度加1
			NodeList[numR].adNode.push_back(numL);//然后后面节点的list记录“是哪些
                                                              //点到过我这里来呢”
		}
	}

	for(int i=0;i<m;i++)	//拓扑排序过程+贪心过程,外层是因为共有m个节点,
                                //所以按照道理是每次找到一个点,所以共m次。
	{
		for(int j=m;j>=1;j--)//反向拓扑,倒过来看谁大(这是贪心的过程),而不是正向找最小。
                                    //这是这道题比较贱的地方。
		{
			if(NodeList[j].outdegree == NONE) continue;//访问过了。。那就跳过。
			if(NodeList[j].outdegree == 0)
			{
				result[numM]=j;
				numM++;
				step++;	//找到没有出度的点,而且是倒过来的,所以
                                        //他就是目前最大的符合题目要求的最“后”的点,进入数组。

				NodeList[j].outdegree = NONE;//找到就标记已访问		
				while(! NodeList[j].adNode.empty())	
				{   //这个循环就是把所有和删除的点有关系的点遍历,然后把它们的出度减一。
                                    //拓扑排序的核心。
					int temp = NodeList[j].adNode.front();
					NodeList[j].adNode.pop_front();
					NodeList[temp].outdegree--;
				}
			        break;//一旦找到这一轮,我们就推出去从头再来,而不是继续,
		//因为我们是在“贪心”!这里也可以用优先队列方式处理,注意是优先队列,而不是FIFO队列。。
			}
		}
	}
	if(step !=m ) cout<<"-1";//好了,如果点数没够,说明,按照一个环自己就出来了,,
                                 //而冷落了别的点,所以说明不存在拓扑序。
	else
	{
		for(int i=m;i>=1;i--)
		{
			int temp = m-i+1;
			sort[result[i]]=temp;	//最后输出的是“按照标号顺序的重量列表”,我们
                                          //拓扑排序的结果是“按照重量顺序的标号列表”。要转换一下。
		}
		cout<<sort[1];
		for(int i=2;i<=m;i++)
		{
			cout<<" "<<sort[i];
		}
	}
	cout<<endl;
}


int main()							//以下是主程序,没有什么可说的了~
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	int t;
	cin>>t;
	while((t--) > 0)
	{
		int m;
		cin>>m;
		caseSolve(m);
	}
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

[一点小积累]
1,memset用法。
2,STL中的find方法。
3,拓扑排序可以查找图中是否含有环。

[尾声]
时间越长就越痛苦。。而且一想到问题可能还是很幼稚的时候就更沮丧。。
不过AC的时候还是很高兴的。。最后推荐这首背景歌曲,今天豆瓣的时候淘到的,真安静~

  

转载于:https://my.oschina.net/o0Kira0o/blog/82396

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值