相邻且有方向的关系只能组成若干条链,合法时答案是链数的阶乘。
P11380 [GESP202412 八级] 排队
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
只检查连通性不够,入度/出度冲突和有向环都会使答案为 0。
二、核心观察
nxt[a]=b 表示 a 后面紧跟 b;pre[b]=a 表示 b 前面紧跟 a。
为什么不丢答案:一条链内部顺序已固定,只剩把所有链整体排列。
pre/nxt
并查集
链数
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
为什么环不合法?
先判断,再点按钮。
孤立点算不算链?
先判断,再点按钮。
五、手推结果
| 检查 | 失败条件 |
|---|---|
| 出度 | 同一人后面跟两人 |
| 入度 | 两人都在某人前 |
| 环 | 并查集已连通 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
struct DSU {
vector<int> parent;
DSU(int n) {
parent.resize(n + 1);
iota(parent.begin(), parent.end(), 0);
}
int find(int i) {
if (parent[i] == i) return i;
return parent[i] = find(parent[i]);
}
bool unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
parent[root_i] = root_j;
return true;
}
return false;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<int> nxt(n + 1, 0);
vector<int> pre(n + 1, 0);
DSU dsu(n);
int edge_count = 0;
bool possible = true;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
if (!possible) continue;
if (a == b) { possible = false; continue; }
// 如果 a 已经排在别人前面,或者 b 前面已经是别人了
if ((nxt[a] != 0 && nxt[a] != b) || (pre[b] != 0 && pre[b] != a)) {
possible = false;
continue;
}
// 如果是已经存在的规则,跳过
if (nxt[a] == b) continue;
// 环检测
if (dsu.find(a) == dsu.find(b)) {
possible = false;
continue;
}
dsu.unite(a, b);
nxt[a] = b;
pre[b] = a;
edge_count++;
}
if (!possible) {
cout << 0 << endl;
return 0;
}
// 剩下的连通块数量(每条边减少一个连通块)
int components = n - edge_count;
ll ans = 1;
for (int i = 1; i <= components; i++) {
ans = (ans * i) % MOD;
}
cout << ans << endl;
return 0;
}