传送门: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;
}