Description
給定一個有 \(n\) 個點 \(m\) 條邊的有向強連通圖。稱一個點是好的當且僅當它到其他點都有且只有一條簡單路徑。如果好的點至少有 \(20\%\) 則輸出所有好的點,否則輸出 -1
。
單個測試點內有多組資料。
\(1\leq T\leq 2\times 10^3,1\leq n\leq 10^5,1\leq m\leq 2\times 10^5,\sum n\leq 10^5,\sum m\leq 2\times 10^5\)。
Solution
考慮如何判斷一個點是否是好的。
先以一個點 \(x\) 為根建出 dfs 樹,如果建不出來顯然不合法,並且如果一條非樹邊不是返祖邊,就說明存在橫叉邊,顯然不合法。
容易發現滿足上面的兩個條件就一定合法,但是這樣做單次是 \(O(n)\) 的,過不了。
先不妨假設 \(x\) 是好點,那麼對於一個點 \(y\),如果他是好點,就說明其子樹裡有且僅有 \(1\) 條返祖邊其祖先為 \(y\) 的祖先,設其為 \(z\to w\)。
然後有一個性質:如果 \(y\) 是好點當且僅當 \(w\) 是好點。
證明:
如果 \(y\) 是好點,由於 \(w\to y\) 只有一條路徑,所以 \(w\to y\) 的子樹的路徑都有且僅有一條,並且 \(y\) 要出 \(w\) 的子樹就必須經過 \(w\),由於 \(y\to w\) 子樹外的點的路徑都是唯一的,所以 \(w\to w\) 子樹外的點的路徑也是唯一的,所以 \(w\) 是好點。
如果 \(w\) 是好點,由於 \(w\) 到所有點路徑唯一且 \(y\) 出 \(w\) 子樹必須經過 \(w\),所以 \(y\) 也是好點。
所以先找到一個為好點的 \(x\),然後做樹上差分對於每個 \(y\) 求出 \(w\) 用並查集合並即可。
然後對於每個好點就一定和 \(x\) 在同一並查集,否則無論如何也到不了 \(x\)。
至於找 \(x\),就每次隨機一個點做 \(100\) 次,失敗的機率是 \(\left(\frac{4}{5}\right)^{100}\),非常小。
時間複雜度:\(O\left(100(n+m)\right)\)。
Code
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 1e5 + 5;
int n, m, fl = 1;
int cnt[kMaxN], sum[kMaxN], fa[kMaxN];
bool ins[kMaxN], vis[kMaxN];
std::vector<int> G[kMaxN];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void unionn(int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy;
}
void dfs1(int u) {
vis[u] = ins[u] = 1;
for (auto v : G[u]) {
if (!vis[v]) {
dfs1(v);
} else {
if (!ins[v]) fl = 0;
else ++cnt[u], --cnt[v], sum[u] += v, sum[v] -= v;
}
}
ins[u] = 0;
}
void dfs2(int u) {
ins[u] = 1;
for (auto v : G[u]) {
if (!ins[v]) {
dfs2(v);
cnt[u] += cnt[v], sum[u] += sum[v];
} else {
}
}
ins[u] = 0;
}
void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i) G[i].clear();
for (int i = 1; i <= m; ++i) {
int u, v;
std::cin >> u >> v;
G[u].emplace_back(v);
}
std::vector<int> vec;
for (int i = 1; i <= n; ++i) vec.emplace_back(i);
std::shuffle(vec.begin(), vec.end(), std::mt19937(std::chrono::steady_clock::now().time_since_epoch().count()));
for (int i = 0; i < std::min(100, (int)vec.size()); ++i) {
int x = vec[i];
for (int i = 1; i <= n; ++i) {
fa[i] = i;
cnt[i] = sum[i] = ins[i] = vis[i] = 0;
}
fl = 1, dfs1(x);
if (!fl) continue;
std::fill_n(ins + 1, n, 0);
dfs2(x);
for (int i = 1; i <= n; ++i) {
if (cnt[i] == 1) {
unionn(i, sum[i]);
}
}
std::vector<int> idx;
for (int i = 1; i <= n; ++i)
if (find(i) == find(x))
idx.emplace_back(i);
if (idx.size() >= (n + 4) / 5) {
for (auto u : idx) std::cout << u << ' ';
std::cout << '\n';
} else {
std::cout << "-1\n";
}
return;
}
std::cout << "-1\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;
}