题目大意
给定一个 $n$ 个点 $m$ 条边的无向图,要求找到图中所有的三元环。
数据范围:$n \le 5 \times 10^4, m \le 2 \times 10^5$。
算法流程
一个 $m$ 条边的无向图中最多有 $O(m \sqrt m)$ 个三元环。本文介绍一种 $O(m \sqrt m)$ 求所有三元环的算法。
先考虑将所有无向边定向,使得所有边的起点度数不小于终点度数,且定向后的图形成一个 DAG。一种合法的定向方法是:对于一条边联结的两个结点,如果他们的度数不同,则只有一种定向方案;如果他们的度数相同,那么就从编号较小的点连向编号较大的点。容易发现构造出的方案是合法的。
我们发现,在一个 DAG 上的三元环一定是一个点向外连两条边,然后另一条边方向任意。也就是说对于三元环 $(u, v, w)$,一定可以重新排列结点,使得边 $(u, v), (u, w), (v, w)$ 存在,并且只有一种排列方式。
然后找三元环算法正式开始。对于每个结点 $u$,我们先将它可以一步走到的结点打上标记。然后,我们枚举 $u$ 可以一步走到的节点 $v$,再枚举 $v$ 可以一步走到的节点 $w$,最后看 $w$ 是否被 $u$ 标记过。如果标记过,则我们找到了一个三元环。由于上一段提到三元环的性质,不难发现这种算法不会重复,不会遗漏。
下面我们来证明该算法的复杂度。我们记每个点的入度为 $\text{in}(i)$,出度为 $\text{out}(i)$,度数为 $\text{deg}(i)$,那么有:$\sum_{u} \text{in}(u) = m, \sum_{u} \text{out}(u) = m, \sum_{u} \text{deg}(u) = 2m$。
该算法的枚举次数等于 $\sum_{u} \sum_{(u, v) \in E} \sum_{(v, w) \in E} 1$,如果我么枚举 $(u, v)$ 的话,它就等于 $\sum_{(u, v) \in E} \text{out}(v)$。
我们根据 $\text{out}(v)$ 分类讨论:
- 如果 $\text{out}(v) \le \sqrt m$,那么 $\sum_{(u, v) \in E} [\text{out}(v) \le \sqrt m]\text{out}(v) \le \sum_{(u, v) \in E} \sqrt m = m \sqrt m$。
- 如果 $\text{out}(v) > \sqrt m$,那么 $\text{deg}(u) \ge \text{deg}(v) \ge \text{out(v)} > \sqrt m$。因为 $\sum_{u} \text{deg}(u) = 2m$,所以这样能一步到达 $v$ 的 $u$ 不会超过 $\sqrt m$ 个。同时因为 $\sum_{u} \text{out}(u) = m$,所以 $\sum_{(u, v) \in E} \text{out}(v) \le m \sqrt m$。
所以算法的复杂度得到了保障。
另外我们注意到 $\sum_{(u, v) \in E} \text{out}(v) = \sum_{u} \text{in}(u) \times \text{out}(u) = \sum_{(u, v) \in E} \text{in}(u)$,所以我们将所有边反向后是不改变复杂度的。也就是说我们也可以从度数小的点向度数大的点连边,复杂度也是 $O(m \sqrt m)$ 的。
代码实现
1 |
|
2 |
|
3 | using namespace std; |
4 | |
5 | const int maxn = 5e4, maxm = 2e5; |
6 | int n, m, u[maxm + 3], v[maxm + 3], deg[maxn + 3], vis[maxn + 3], e[maxn + 3]; |
7 | int tot, ter[maxm + 3], nxt[maxm + 3], lnk[maxn + 3]; |
8 | |
9 | void add(int u, int v) { |
10 | ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot; |
11 | } |
12 | |
13 | int main() { |
14 | scanf("%d %d", &n, &m); |
15 | for (int i = 1; i <= m; i++) { |
16 | scanf("%d %d", &u[i], &v[i]); |
17 | deg[u[i]]++, deg[v[i]]++; |
18 | } |
19 | for (int i = 1; i <= m; i++) { |
20 | if (deg[u[i]] < deg[v[i]] || (deg[u[i]] == deg[v[i]] && u[i] > v[i])) { |
21 | swap(u[i], v[i]); |
22 | } |
23 | add(u[i], v[i]); |
24 | } |
25 | int cnt = 0; |
26 | for (int i = 1; i <= n; i++) { |
27 | for (int j = lnk[i]; j; j = nxt[j]) { |
28 | vis[ter[j]] = i, e[ter[j]] = j; |
29 | } |
30 | for (int j = lnk[i], u; j; j = nxt[j]) { |
31 | for (int k = lnk[u = ter[j]], v; k; k = nxt[k]) { |
32 | printf("3 Membered Ring #%d: (%d %d %d)\n", ++cnt, j, k, e[v]); |
33 | } |
34 | } |
35 | } |
36 | printf("Number of 3 Membered Rings: %d\n", cnt); |
37 | return 0; |
38 | } |