JZOJ[5445]字典序 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/er111er/article/details/78937379

[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 2

Sample Output
1 5 3 4 2

Data 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;
}

猜你喜欢

转载自blog.csdn.net/er111er/article/details/78937379