SeekLuna P1362 拓扑排序 3 题解

拓扑排序 3

题意

有一个由 \(n\) 个节点(编号分别为 \(1,2 \dots n\))和 \(m\)有向边组成的有向无环图,第 \(i\) 条边从 \(u_i\) 连向 \(v_i\)

请求出所有 \(1\)\(n\) 的全排列,使得对于任意一条从节点 \(u_i\) 至节点 \(v_i\) 的有向边,\(u_i\) 在排列中的位置总是在 \(v_i\) 在排列中的位置的前面。

按照字典序从小到大输出。

数据范围

  • \(1 \leqslant n \leqslant 20\)
  • \(1 \leqslant m \leqslant \frac{n \times (n - 1)}{2}\)
  • 保证图无环且无重边,保证答案不超过 \(2 \times 10^5\)

思路

首先排除普通的全排列搜索,时间复杂度爆炸了。(高级全排列搜索)

分析一下题意,其实就是这个点在加入答案序列时,所有可以连向它的点都必须已经加入到了答案序列中。

这样一看,很明显是有一个拓扑序在的。那么我们就可以考虑使用拓扑排序。

由于这道题要求出所有满足要求的全排列,那么我们就不能用 while 循环版本的拓扑排序,要用类 dfs 的拓扑排序来做,每次dfs时枚举一下选择哪个合法的点加入答案序列中,最后输出即可。

即dfs操作大致为:

  • 枚举所有入度为 \(0\) 的点,将其加入答案序列中。
  • 将所有从这个点连出去的边删除,同时记录一下又有哪些点的入度变为了 \(0\),将其加入入度为 \(0\) 的点的序列当中
    • 其实这里并不是真正的删除(至少我的做法不是),只是将那些点的入度暂时减少 \(1\),当下一层搜索彻底结束时,需要将这些点入度重新加 \(1\),即回溯
    • 这里为了保证字典序,进行下一层搜索之前需要排序一下。
  • 进行下一层的搜索

复杂度

  • 时间:\(O((n^2\ \log\ n)\times 2 \times 10^5)\)
    • 每次搜索 \(O(n^2\ \log\ n)\),题目保证答案数量不超过 \(2 \times 10^5\)
  • 空间:\(O(n+m)\)
    • 记录答案等 \(O(n)\)vector 存储邻接表 \(O(n+m)\)

Code

点击查看代码

有点丑啊……

#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 30;

int n, m, stk[N], top, x, y, f[N], no, b[N], ans[N];
vector<int> a[N];

void dfs (int stk[], int top, int no) {
  if (!no) { // 一种情况的结束
    for (int i = 0; i < n; i++) { // 输出答案
      cout << ans[i] << ' ';
    }
    cout << '\n';
    return ;
  }
  int t;
  for (int j = 0; j < top; j++) {
    ans[n - no] = stk[j]; // stk 中存储了所有的入度为 0 的点
    t = stk[j]; // 记录当前选择的点
    f[t] = 1;
    int stk_[N], top_ = 0; // 记录所有入度为 0 的点,记得把 stk 中的其他点算进去
    for (int i = 0; i < top; i++) {
      if (i != j) {
        stk_[top_++] = stk[i];
      }
    }
    for (int i = 0; i < a[t].size(); i++) {
      if (!f[a[t][i]]) {
        b[a[t][i]]--; // 入度减 1
        if (!b[a[t][i]]) { // 产生了入度为 0 的点
          stk_[top_++] = a[t][i]; // 压入 stk_
        }
      }
    }
    sort(stk_, stk_ + top_); // 记得要排序
    dfs(stk_, top_, no - 1); // 下一层搜索
    // 下面这一段是回溯
    for (int i = 0; i < a[t].size(); i++) {
      if (!f[a[t][i]]) {
        b[a[t][i]]++;
      }
    }
    f[t] = 0;
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  no = n;
  for (int i = 1; i <= m; i++) {
    cin >> x >> y;
    a[x].push_back(y); // vector 存储邻接矩阵
    b[y]++; // 入度的统计
  }
  for (int i = 1; i <= n; i++) {
    if (!b[i]) { // 第一轮的入度为 0 的点
      stk[top++] = i;
    }
  }
  dfs(stk, top, no); // 调用 dfs
  return 0;
}
posted @ 2023-03-14 10:19  wnsyou  阅读(35)  评论(0编辑  收藏  举报