无向图三元环计数
题目背景
无向图 $G$ 的三元环指的是一个 $G$ 的一个子图 $G_0$,满足 $G_0$ 有且仅有三个点 $u, v, w$,有且仅有三条边 $\langle u, v \rangle, \langle v, w \rangle, \langle w, u \rangle$。两个三元环 $G_1, G_2$ 不同当且仅当存在一个点 $u$,满足 $u \in G_1$ 且 $u \notin G_2$。
题目描述
给定一个 $n$ 个点 $m$ 条边的简单无向图,求其三元环个数。
输入格式
每个测试点有且仅有一组测试数据。
输入的第一行是用一个空格隔开的两个整数,分别代表图的点数 $n$ 和边数 $m$。
第 $2$ 到第 $(m + 1)$ 行,每行两个用空格隔开的整数 $u, v$,代表有一条连接节点 $u$ 和节点 $v$ 的边。
输出格式
输出一行一个整数,代表该图的三元环个数。
样例 #1
样例输入 #1
3 3
1 2
2 3
3 1
样例输出 #1
1
样例 #2
样例输入 #2
5 8
1 2
2 3
3 5
5 4
4 2
5 2
1 4
3 4
样例输出 #2
5
提示
【样例 2 解释】
共有 $5$ 个三元环,每个三元环包含的点分别是 $\{1, 2, 4\}, \{2, 3, 4\}, \{2, 3, 5\}, \{2, 4, 5\}, \{3, 4, 5\}$。
【数据规模与约定】
本题采用多测试点捆绑测试,共有两个子任务。
- Subtask 1(30 points):$n \le 500$,$m \le {10}^3$。
- Subtask 2(70 points):无特殊性质。
对于 $100\%$ 的数据,$1 \le n \le 10^5$,$1 \le m \le 2 \times {10}^5$,$1 \le u, v \le n$,给出的图不存在重边和自环,但不保证图连通。
【提示】
- 请注意常数因子对程序效率造成的影响。
解题思路
先给出根号分治的暴力做法。
对于一个三元环 $(u,v,w)$,我们可以先枚举边 $(u,v)$,然后枚举 $u$(或 $v$)的邻点 $w$,若 $w$ 也是 $v$(或 $u$)的邻点,那么就得到三元环 $(u,v,w)$ 了。如果是菊花图的话会被卡到 $O(m^2)$。由于我们只关心同时与 $u$ 和 $v$ 相邻的点的个数,为此我们可以给每个点开一个 std::bitset
标记其邻点,那么两个节点对应的 std::bitset
进行与运算后 $1$ 的个数就是共同相邻点的个数。但空间复杂度是 $O(n^2)$。
这时候就可以根号分治了,设一个阈值 $B$,度数超过 $B$ 的节点定义为重节点,否则为轻节点。容易知道重节点的数量不超过 $O\left(\frac{m}{B}\right)$,为此我们可以给每个重节点开一个 std::bitset
标记其邻点,空间复杂度为 $O\left(\frac{m}{B} \cdot n\right)$。根据边 $(u,v)$ 两端点的种类不同有相应的枚举策略,不失一般性假设 $u$ 的度数不超过 $v$ 的度数:
- 若 $u$ 的度数超过 $B$,意味着 $u$ 和 $v$ 都是重节点,直接将
std::bitset
进行与运算。时间复杂度为 $O\left(\frac{n}{w}\right)$。 - 否则 $u$ 的度数不超过 $B$,
- $v$ 的度数也不超过 $B$,直接暴力枚举 $u$ 的邻点 $w$ 并判断 $v$ 是否与 $w$ 相邻。时间复杂度为 $O(B)$ 或 $O(B \log{n})$(取决于用什么容器存储邻点)。
- $v$ 的度数超过 $B$,意味着 $v$ 是重节点,暴力枚举 $u$ 的邻点 $w$ 并判断 $v$ 对应
std::bitset
的第 $w$ 位是否被标记。时间复杂度为 $O(B)$。
最后代码中 $B$ 的取值设为 $\frac{n}{w}$,是可以过的。还有需要注意的是该方法每个三元环会被统计 $3$ 次,最后答案需要除以 $3$。
AC 代码如下,时间复杂度为 $O\left(\frac{nm}{w}\right)$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5, M = 1500;
int x[N], y[N];
vector<int> g[N];
bool vis[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
g[x[i]].push_back(y[i]);
g[y[i]].push_back(x[i]);
}
map<int, bitset<N>> mp;
for (int i = 1; i <= n; i++) {
if (g[i].size() > M) {
for (auto &j : g[i]) {
mp[i][j] = 1;
}
}
}
LL ret = 0;
for (int i = 1; i <= m; i++) {
if (g[x[i]].size() > g[y[i]].size()) swap(x[i], y[i]);
if (g[x[i]].size() <= M) {
if (g[y[i]].size() <= M) {
for (auto &v : g[y[i]]) {
vis[v] = true;
}
for (auto &v : g[x[i]]) {
if (vis[v]) ret++;
}
for (auto &v : g[y[i]]) {
vis[v] = false;
}
}
else {
auto &p = mp[y[i]];
for (auto &v : g[x[i]]) {
if (p[v]) ret++;
}
}
}
else {
ret += (mp[x[i]] & mp[y[i]]).count();
}
}
cout << ret / 3;
return 0;
}
再给出更高效的做法,正常情况下是想不到的。
给每条边确定一个方向,规定度数小的节点指向度数大的节点,如果两个节点的度数相等,则编号小的节点指向编号大的节点。最后得到的是一个有向无环图,否则如果有环的话,那么必定会与前面的定义产生矛盾。
对于三元环 $(u,v,w)$,不失一般性假设三个节点的度数依次递增,度数相同则编号依次递增,那么在 DAG 中一定有 $u \to v$,$v \to w$,$u \to w$。所以枚举 $u$ 指向的 $v$,再枚举 $v$ 指向的 $w$,最后判断 $u$ 是否指向 $w$ 即可确定一个三元环。该做法的时间复杂度是 $O(m \sqrt{m})$,下面给出证明。
考虑每条边对复杂度的贡献,其实就是每个点的入度乘以出度,可以表示为 $\sum\limits_{i=1}^{m}{d_{v_i}}$,其中 $v_i$ 指第 $i$ 条边被指向的点,$d_v$ 指点 $v$ 的入度。分情况讨论,如果在初始的无向图中点 $u$ 的度数不超过 $\sqrt{m}$,那么在 DAG 中其出度也不会超过 $O(\sqrt{m})$,这样的点最多有 $O(m)$ 个,因此复杂度就是 $O(m \sqrt{m})$。如果在初始的无向图中点 $u$ 的度数超过 $\sqrt{m}$,由于图中度数超过 $\sqrt{m}$ 的点最多有 $O(\sqrt{m})$ 个,因此复杂度就是 $O(m \sqrt{m})$。
AC 代码如下,时间复杂度为 $O(m \sqrt{m})$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
int x[N], y[N];
int deg[N];
vector<int> g[N];
bool vis[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
deg[x[i]]++, deg[y[i]]++;
}
for (int i = 1; i <= m; i++) {
if (deg[x[i]] < deg[y[i]] || deg[x[i]] == deg[y[i]] && x[i] < y[i]) g[x[i]].push_back(y[i]);
else g[y[i]].push_back(x[i]);
}
LL ret = 0;
for (int i = 1; i <= n; i++) {
for (auto &j : g[i]) {
vis[j] = true;
}
for (auto &j : g[i]) {
for (auto &k : g[j]) {
if (vis[k]) ret++;
}
}
for (auto &j : g[i]) {
vis[j] = false;
}
}
cout << ret;
return 0;
}
参考资料
题解 P1989 【【模板】无向图三元环计数】 - 洛谷专栏:https://www.luogu.com.cn/article/oz2l2vl2
环计数问题 - OI Wiki:https://oi-wiki.org/graph/rings-count/
标签:度数,int,复杂度,sqrt,计数,无向,三元,节点 From: https://www.cnblogs.com/onlyblues/p/18374420