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)\)
- 记录答案等 \(O(n)\),
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;
}