[JZOJ5445]字典序 题解
题面
Description
你需要构造一个1~n的排列,使得它满足m个条件,每个条件形如(ai,bi),表示ai必须在bi前面。在此基础上,你需要使它的字典序最小。Input
第一行两个正整数n,m。接下来m行每行两个数ai,bi。Output
输出一行n个整数表示答案。如果不存在这样的排列,输出-1。Sample Input
5 4
5 4
5 3
4 2
3 2Sample Output
1 5 3 4 2Data Constraint
对于20%的数据,n,m<=10。
对于40%的数据,n,m<=200。
对于60%的数据,n,m<=1000。
对于100%的数据,n,m<=100000。
题解 & 思路
尝试建图。把n当作点的数量,m为边的数量,显然对于每一个(ai, bi)就是从a向b连一条有向边,满足所有(ai, bi)的排列即为该DAG的拓扑序,因为拓扑序中必定保证一个点出现时,其前驱节点全部已经出现过,也就实现了题目所给要求。
但是拓扑序是不只有一个的,题目给定要求寻找字典序最小的排列。
考虑一个拓扑序,若要使其字典序最小,可以贪心使得编号最小的节点优先计入拓扑序,但是这样是否会破坏拓扑序的性质呢?在一般的拓扑序中,我们用一个栈来储存现在入度为0的节点,这些点按照他们存进栈的次序倒序计入拓扑序,我们可以控制这个过程,使得出栈的点编号为最小的,因为此时栈中每个节点入度都为0,因此这时候无论取出哪个节点加入拓扑序都不会破坏其性质,于是我们就可以选择编号最小的节点加入拓扑序了。
由于这个栈已经不注重先后顺序,只看编号大小,我们可以将其替换为优先队列,以优先队列作为容器进行存取。
一定要理解,拓扑序中的栈仅是一个容器,栈中元素出栈顺序是任意的!因此才会有多个排列。
代码:
#include <queue> //使用STL优先队列需要头文件queue
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int N = 100007, M = 100007; //预留空间以防RE
int n, m, u, v, tot = 0;
int to[M], nx[M], st[N], rd[N], vis[N], ans[N];
priority_queue<int, vector<int>, greater<int> > q; //greater<int>使得每次取出的值是最小的,如果是priority_queue<int>则每次取出的值为最大的,注意greater<int>的后面需要空格,某些编译器会将其当作>>处理导致编译失败
void init()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
to[i] = v, nx[i] = st[u], st[u] = i; //前向星存图
rd[v]++; //入度+1
}
for (int i = 1; i <= n; i++)
if (!rd[i]) //入度为0的节点
{
q.push(i); //存入优先队列
vis[i] = 1; //该点已经排序好了
}
}
void solve()
{
while (!q.empty())
{
int u = q.top();
q.pop();
ans[++tot] = u; //加入拓扑序
for (int i = st[u]; i; i = nx[i]) //枚举出边
{
int v = to[i];
if (!vis[v]) //该点还未排好序
{
rd[v]--; //删除出边,入度-1
if (!rd[v]) //入度为0
{
q.push(v); //存入队列,相当于一般拓扑排序里的入栈操作
vis[v] = 1; //该点已排好序
}
}
}
}
if (tot < n) //有一部分节点无法排序
printf("-1\n"); //不存在如此的拓扑序
else //存在
{
for (int i = 1; i <= tot; i++)
printf("%d ", ans[i]); //输出排列
putchar('\n'); //强迫症换行
}
}
int main()
{
freopen("dictionary.in", "r", stdin);
freopen("dictionary.out", "w", stdout);
init();
solve();
fclose(stdin);
fclose(stdout);
return 0;
}