无向图三元环计数

本篇博客参考这篇博客,然后根据自己的理解补了一点小细节,如果想看原文可以去这里


题意

给定一张简单无向图,请求出图中的三元环个数,其中三元环的定义是一个三元组\((i,j,k)\),其中\((i,j)\),\((j,k)\),\((k,i)\)均有边直接相连

做法

首先对所有边进行定向,对于任何一条边,从度数小的边连向度数大的边,如果度数相同,则从编号小的边连向编号大的边.
注意这里的度数是指无向图中的度数,也就是一条边同时会给这条边的两个端点增加度数

然后我们通过这种方法就能得到一张有向无环图,我们枚举每个点\(u\),然后将在有向图\(u\)能直接到达的边标记为被\(u\)访问过,然后再枚举所有\(u\)在有向图能直接到达的点\(w\),如果\(w\)相邻的点中有一个点\(v\)\(u\)访问过,那么这就是一个三元环

证明

有向无环

首先证明我们得到的图是一张有向无环图

如果在我们定向后的图中存在环,那么在原来的无向图中这个也一定是个环.我们假设一个环为\((1->2->3->1)\),那么按照我们的连边规则,那么会有

\[deg[1]>=deg[2]>=deg[3]>=deg[1] \]

发现这个不等式成立时只有\(deg[1]=deg[2]=deg[3]\)这一种情况,但是当度数相等时我们按照编号大小连边,又因为\(1<2<3<1\)这个不等式显然不成立,所以这个环无论如何都不会存在

综上,这张图有向,而且没有环,所以是一张有向无环图(嗯

时间复杂度

我们会枚举所有点,并且把所有枚举到的点都打上能到达的标记,所以复杂度为\(O(n+m)\)

然后我们考虑枚举一个点能到的所有点再枚举它能到的所有点,这个本质上是枚举\(\sum_{i=1}^{n} out_i\)

首先考虑当\(out_v \leq \sqrt{m}\)的情况,因为\(u->v\)有一条边,所以\(deg_u \geq deg_v\),而这种点的数量显然不超过\(m\)数量级,所以这部分的复杂度为\(O(n\sqrt{m})\)

然后考虑当\(out_v \geq \sqrt{m}\)时的情况,,因为\(u->v\)有一条边,所以\(deg_u \geq deg_v\),而这种点的数量显然不超过\(\sqrt{m}\)数量级,所以这部分的复杂度为\(O(m\sqrt{m})\)

综上,时间复杂度为\(O(\max(n,m) \log m)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int deg[N], vis[N], n, m, ans;
struct E { int u, v; } e[N * 3];
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1 ; i <= m ; ++ i) {
        scanf("%d%d", &e[i].u, &e[i].v);
        ++ deg[e[i].u], ++ deg[e[i].v];
    }
    for(int i = 1 ; i <= m ; ++ i) {
        int u = e[i].u, v = e[i].v;
        if(deg[u] < deg[v] || (deg[u] == deg[v] && u > v)) swap(u, v);
        g[u].push_back(v);
    }
    for(int x = 1 ; x <= n ; ++ x) {
        for(auto y: g[x]) vis[y] = x;
        for(auto y: g[x])
            for(auto z: g[y])
                if(vis[z] == x)
                    ++ ans;
    }
    printf("%d\n", ans);
}
posted @ 2022-04-28 11:11  兮水XiShui  阅读(242)  评论(0)    收藏  举报