CF1361E James and the Chase 题解

Description

给定一个有 nn 个点 mm 条边的有向强连通图。称一个点是好的当且仅当它到其他点都有且只有一条简单路径。如果好的点至少有 20%20\% 则输出所有好的点,否则输出 -1

单个测试点内有多组数据。
1T2×103,1n105,1m2×105,n105,m2×1051\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

考虑如何判断一个点是否是好的。

先以一个点 xx 为根建出 dfs 树,如果建不出来显然不合法,并且如果一条非树边不是返祖边,就说明存在横叉边,显然不合法。

容易发现满足上面的两个条件就一定合法,但是这样做单次是 O(n)O(n) 的,过不了。

先不妨假设 xx 是好点,那么对于一个点 yy,如果他是好点,就说明其子树里有且仅有 11 条返祖边其祖先为 yy 的祖先,设其为 zwz\to w

然后有一个性质:如果 yy 是好点当且仅当 ww 是好点。

证明:

如果 yy 是好点,由于 wyw\to y 只有一条路径,所以 wyw\to y 的子树的路径都有且仅有一条,并且 yy 要出 ww 的子树就必须经过 ww,由于 ywy\to w 子树外的点的路径都是唯一的,所以 www\to w 子树外的点的路径也是唯一的,所以 ww 是好点。

如果 ww 是好点,由于 ww 到所有点路径唯一且 yyww 子树必须经过 ww,所以 yy 也是好点。

所以先找到一个为好点的 xx,然后做树上差分对于每个 yy 求出 ww 用并查集合并即可。

然后对于每个好点就一定和 xx 在同一并查集,否则无论如何也到不了 xx

至于找 xx,就每次随机一个点做 100100 次,失败的概率是 (45)100\left(\frac{4}{5}\right)^{100},非常小。

时间复杂度:O(100(n+m))O\left(100(n+m)\right)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#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;
}

CF1361E James and the Chase 题解
https://sobaliuziao.github.io/2024/04/08/post/10dec6d5.html
作者
Egg_laying_master
发布于
2024年4月8日
许可协议