QOJ #14432. Cyclic Topsort 题解
Description
给定一个有向图,图中有 \(n\) 个顶点和 \(m\) 条边。任务是找到一个从 \(1\) 到 \(n\) 的不同整数构成的最长序列 \(a\),使得以下条件成立:
- 设序列 \(a\) 的长度为 \(l\)。那么对于所有整数 \(i \in [2, l]\),以及所有顶点 \(v\),如果存在一条边 \((v, a_i)\),则必定存在一个索引 \(j < i\),使得 \(a_j = v\)。
\(n,m\leq 3\times 10^5\)。
Solution
先不管 \(a_1\) 的特殊性跑一遍拓扑排序,则这样能够入队的点永远能够入队,可以将其删掉。
加上 \(a_1\) 后,等价于在现在跑完的图上再将 \(a_1\) 放到队列里继续跑拓扑排序,暴力枚举 \(a_1\) 可以得到一个 \(O(n^2)\) 的做法。
设 \(S_u\) 表示 \(a_1=u\) 时相比初始必选点的增量集合,那么就是求 \(\max|S_u|\)。
有个结论是所有不同的 \(S_u\) 和 \(S_v\) 一定包含或者不交。
证明
考虑钦定 \(|S_u|\leq |S_v|\),如果 \(S_u\) 与 \(S_v\) 有交且互不包含,则一定找到两条不同的边 \((x_1,y),(x_2,y)\),使得 \(y\neq u,y\neq v\),\(x_1\) 和 \(x_2\) 分别在 \(S_u\setminus S_v\) 和 \(S_v\setminus S_u\) 中,且 \(y\) 同时在 \(S_u\) 和 \(S_v\) 中,但是出现这种情况的话两边一定不能拓展到 \(y\),也就矛盾了。
根据这个结论,如果 \(v\in S_u\),则 \(S_v\subseteq S_u\),就不再需要计算 \(a_1=v\) 的答案了。所以可以随机打乱排列,按顺序枚举排列的每一个点,如果当前点没有被拓展到过,则将这个点加入队列并计算答案,再更新是否拓展过的标记。
这个做法的期望复杂度是 \(O((n+m)\log n)\) 的。证明考虑对于每个点 \(u\),计算其被拓展的期望次数。先按照集合的包含关系建树,把 \(u\) 的所有祖先 \(p_1,p_2,\ldots,p_t\),则 \(u\) 被拓展的次数是在排列中这些祖先节点中 \(dep\) 的前缀最小值数量,这个是经典结论,为 \(O(\log n)\),所以总复杂度就是 \(O((n+m)\log n)\)。
Code
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 3e5 + 5;
int n, m, cnt;
int deg[kMaxN], _deg[kMaxN];
bool vis[kMaxN];
std::vector<int> G[kMaxN];
std::mt19937 rnd(std::random_device{}());
int solve(int s) {
std::queue<int> q;
std::vector<int> vid;
q.emplace(s);
int ret = 0;
for (; !q.empty();) {
int u = q.front(); q.pop();
vis[u] = 1, ++ret;
for (auto v : G[u]) {
if (v == s) continue;
if (deg[v] == _deg[v]) vid.emplace_back(v);
if (!--deg[v]) q.emplace(v);
}
}
for (auto i : vid) deg[i] = _deg[i];
return ret;
}
void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int u, v;
std::cin >> u >> v;
G[u].emplace_back(v);
++deg[v];
}
{
std::queue<int> q;
for (int i = 1; i <= n; ++i)
if (!deg[i])
q.emplace(i);
for (; !q.empty();) {
int u = q.front(); q.pop();
vis[u] = 1, ++cnt;
for (auto v : G[u]) {
if (!--deg[v]) q.emplace(v);
}
}
for (int i = 1; i <= n; ++i) _deg[i] = deg[i];
}
std::vector<int> id;
for (int i = 1; i <= n; ++i) id.emplace_back(i);
std::shuffle(id.begin(), id.end(), rnd);
int ans = cnt;
for (auto i : id) {
if (!vis[i]) {
ans = std::max(ans, cnt + solve(i));
}
}
std::cout << ans << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}