首页 > 其他分享 >无向图三元环计数

无向图三元环计数

时间:2024-08-22 17:48:16浏览次数:12  
标签:度数 int 复杂度 sqrt 计数 无向 三元 节点

无向图三元环计数

题目背景

无向图 $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

相关文章

  • 【解题报告】十二重计数法
    I:球之间互不相同,盒子之间互不相同。对于这部分的计数,很显然方案总数是\(nm\)II:球之间互不相同,盒子之间互不相同,每个盒子至多装一个球。对于这部分的计数,每个盒子只有\(0/1\)两种状态对于每种都需要在没选出来的里做出选择,方案数也就是\(\prod_{i=0}^{n-1}(m-i)\)III......
  • C#联合halcon实现connection后的物料上色、物料计数、物料框选
    一、效果预览二、实现步骤三、代码部分ReadImageThresholdHalconWindowShowImageRectangleDef类库GenRectanglepublicstaticvoidGenRectangle(HObjectRegion,outHObjectExternalRegion,outRectangleDefrectangleDef,boolisMargin,HWindowhWindow)......
  • MySQL子查询、WITH AS、LAG查询统计数据实战
    需求给出一个比较常见的统计类业务需求:统计App(包括iOS和Android两大类)每日新注册用户数、以及累计注册用户数。数据库采用MySQL,根据上面的需求,不难设计表如下:createtableos_day_count(stat_datevarchar(10)notnullcomment'统计日期',osvarcha......
  • 《面板计数模型及 Stata 具体操作步骤》
    目录一、文献综述二、理论原理三、实证模型四、稳健性检验五、程序代码及解释六、代码运行结果一、文献综述面板计数模型作为一种重要的统计分析工具,在众多学科领域中都展现出了强大的应用价值。在经济学领域,Cameron和Trivedi(2005)指出,面板计数模型可以有效地用......
  • 【Leetcode 1370 】 数组序号转换—— 桶计数
    给你一个字符串 s ,请你根据下面的算法重新构造字符串:从 s 中选出 最小 的字符,将它 接在 结果字符串的后面。从 s 剩余字符中选出 最小 的字符,且该字符比上一个添加的字符大,将它 接在 结果字符串后面。重复步骤2,直到你没法从 s 中选择字符。从 s 中选出 ......
  • 20240820:组合计数(2)
    二项式反演引入错排问题:\(n\)个人排列。第\(i\)个人不能在位置\(i\),求合法排列数。钦定\(k\)个人在自己位置上:\[\sum_{k=0}^n(-1)^k\begin{pmatrix}n\\k\end{pmatrix}(n-k)!\]定义\(f(n)\)为\(n\)个人的排列数,\(g(n)\)为\(n\)个人的错排数。\[\begin{ali......
  • 小小的引用计数,大大的性能考究
    本文基于Netty4.1.56.Final版本进行讨论在上篇文章《聊一聊Netty数据搬运工ByteBuf体系的设计与实现》中,笔者详细地为大家介绍了ByteBuf整个体系的设计,其中笔者觉得Netty对于引用计数的设计非常精彩,因此将这部分设计内容专门独立出来。Netty为ByteBuf引入了引......
  • 计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人
     车辆跟踪及测距 该项目一个基于深度学习和目标跟踪算法的项目,主要用于实现视频中的目标检测和跟踪。该项目使用了YOLOv5目标检测算法和DeepSORT目标跟踪算法,以及一些辅助工具和库,可以帮助用户快速地在本地或者云端上实现视频目标检测和跟踪!教程博客_传送门链接-------......
  • 在EFCore中多对多关系的设计数据插入与查询
    学生类StudentpublicclassStudent{publicintId{get;set;}publicstringName{get;set;}publicintAge{get;set;}publicList<Teacher>Teachers{get;set;}=newList<Teacher>();}老师类TeacherpublicclassTeacher{......
  • 面试题:在Java中,JVM(Java虚拟机)的内存模型是如何设计的?请详细解释堆(Heap)、栈(Stack)、方法
    面试题:在Java中,JVM(Java虚拟机)的内存模型是如何设计的?请详细解释堆(Heap)、栈(Stack)、方法区(MethodArea)以及程序计数器(ProgramCounterRegister)的作用和它们之间的关系。更多答案在这里,手机或电脑浏览器就可以打开,面霸宝典【全拼音】.com这里可以优化简历,模拟面试,企业项......