并非无向图三元环计数 2
dongzirui0817 · · 算法·理论
有所听闻 并非无向图三元环计数。所以今天不讲三元环,更不讲
先导入一下。
一元环计数
等价于自环数,这你肯定会求。
二元环计数
求一下
三元环计数 P1989
去看 这个题解,时间复杂度
四元环计数 LibreOJ 191
主角登场。结论是可以做到
和三元环一样,我们还是给边定个方向。度数小的点向度数大的点连边,度数相同时,编号小的点向编号大的点连边。
当然这里度数大连小还是小连大没关系,编号亦然。
然后我们再用现在的有向图建个反图,待会会知道它的作用的。
设四元环由四个点
a\to b 、b\to d 、a\to c 、c\to d
此时就是找有多少条
for (auto v : e[u])
for (auto w : e[v])
if (w ^ u) ans += c[w][0], ++c[w][0];
a \to b 、b \to c 、c \to d 、a \to d
其实就是找
for (auto v : re[u])
for (auto w : e[v])
if (w ^ u) ans += c[w][1], ++c[w][1];
ans /= 2; // 注意,这里 ans 不一定整除 2,这里当 /= 不是整除
可以发现只有这三种情况。做完了。
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, M = 2e5 + 10;
int n, m;
int u[M], v[M], d[N];
vector <int> e[N], re[N];
ll ans = 0, c[N][2];
// 十年 OI 一场空,但实测 int 也能过
/*
u[i], v[i]:边 (u, v)
d[i]:i 的度数
e: 有向图
re:反边
c[i][0]:Case 1, 2
c[i][1]:Case 3
*/
int main() {
scanf("%d%d", &n, &m);
for (int i = 1 ; i <= m ; i++) {
scanf("%d%d", &u[i], &v[i]);
d[u[i]]++;
d[v[i]]++;
}
for (int i = 1 ; i <= m ; i++) {
if (d[u[i]] < d[v[i]] || (d[u[i]] == d[v[i]] && u[i] < v[i])) swap(u[i], v[i]);
e[v[i]].push_back(u[i]);
re[u[i]].push_back(v[i]);
}
for (int u = 1 ; u <= n ; u++) {
ll t1 = 0, t2 = 0, t3 = 0;
for (auto v : e[u]) // Case 1
for (auto w : e[v])
if (w ^ u) t1 += c[w][0], ++c[w][0];
for (auto v : re[u]) // Case 2, 3
for (auto w : e[v])
if (w ^ u)
t2 += c[w][0], t3 += c[w][1], ++c[w][1];
for (auto v : e[u]) // 清空
for (auto w : e[v]) c[w][0] = 0;
for (auto v : re[u])
for (auto w : e[v]) c[w][1] = 0;
ans = ans + t1 * 2 + t2 * 2 + t3; // 这里把 t1 t2 t3 全乘 2,输出时除以 2
}
printf("%lld\n", ans / 2);
return 0;
}
::: :::success[省空间版]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, M = 2e5 + 10;
int n, m;
int u[M], v[M], d[N];
vector <int> e[N], re[N];
ll ans = 0, c[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1 ; i <= m ; i++) {
scanf("%d%d", &u[i], &v[i]);
d[u[i]]++;
d[v[i]]++;
}
for (int i = 1 ; i <= m ; i++) {
if (d[u[i]] < d[v[i]] || (d[u[i]] == d[v[i]] && u[i] < v[i])) swap(u[i], v[i]);
e[v[i]].push_back(u[i]);
re[u[i]].push_back(v[i]);
}
for (int u = 1 ; u <= n ; u++) {
for (auto v : e[u]) // Case 1
for (auto w : e[v])
ans += c[w] * 2, ++c[w];
for (auto v : re[u]) // Case 2
for (auto w : e[v])
if (w ^ u) ans += c[w] * 2;
for (auto v : e[u])
for (auto w : e[v]) c[w] = 0;
for (auto v : re[u]) // Case 3
for (auto w : e[v])
if (w ^ u) ans += c[w], ++c[w];
for (auto v : re[u])
for (auto w : e[v]) c[w] = 0;
}
printf("%lld\n", ans / 2);
return 0;
}
:::
:::info[时间复杂度证明]{open}
时间复杂度
第一种情况下,
第二、三种情况下,
所以时间复杂度
:::error[for 的顺序不能搞反]{open}
不能
for (auto v : e[u])
for (auto w : re[v])
if (w ^ u) ans += c[w];
这样写当
你可能会说:“三元环计数我都没咋用,四元环计数岂不更 useless?”
其实还是有点用的但不多,让我们看一下下面这道例题。
:::info[ABC260F Find 4-cycle]{open}
给一个左边