Processing math: 100%

不常用的黑科技——「三元环」

万恶之源:

给定一张无重边、无自环的无向图(点数为n,边数为m,且n,m同阶),问有多少个无序三元组(i,j,k),使得存在:

1. 有一条连接i,j的边

2. 有一条连接j,k的边

3. 有一条连接k,i的边

举个例子:

这张图中有三个三元环:(1,2,3),(1,3,4),(3,4,5)

有一个显然的基于度数的乱搞的做法(本人并没有写过……),可以在这里阅读到:jiachin_zhao [hdu 6184 Counting Stars](三元环计数)

这里介绍一种十分优秀的三元环计数方法:

首先要对所有的无向边进行定向,对于任何一条边,从度数大的点连向度数小的点,如果度数相同,从编号小的点连向编号大的点

此时这张图是一个有向无环图

之后枚举每一个点u,然后将u的所有相邻的点都标记上“被u访问了”,然后再枚举u的相邻的点v,然后再枚举v的相邻的点w,如果w存在“被u访问了”的标记,那么(u,v,w)就是一个三元环了

而且每个三元环只会被计算一次

那么现在就只需要证明三件事:

1. 定向后的图是一个有向无环图

以上图为例,用degu表示u在原图中的度数,则degadegbdegcdegddegedega

dega=degb=degc=degd=dege=dega

对于度数相同的点,是按照编号从小到大连边的,则abcdea

a=b=c=d=e

然而点的编号是1n的全排列,因此不会产生如上的事情,既不存在环

由于这张图有向,而且没有环,因此这张图就是有向无环图

2. 时间复杂度是O(mm)(在此认为n,m同阶)

对于“打标记”这个操作,每个点都会将所有的出边遍历一遍,那么这里的时间复杂度为O(n+m)

对于访问u,然后访问v,然后访问w,可以这么考虑:对于每条边(uv),对时间复杂度的贡献为outv​,其中outv表示v的出边个数

这样就转化为了求ni=1outi

不妨将outv分类讨论

1. outvm​,那么由于u连接v,因此degudegv​,这样的uu的个数是O(n)的,因此这里的时间复杂度为O(nm)

2. outv>m,那么由于u连接v,因此degudegv,既degu>m​,这样的u的个数是O(m)的,因此这里的时间复杂度为O(mm)=O(mm)

因此时间复杂度为O(n+m+nm+mm)=O(mm)

3. 每个三元环只会被统计一次

三元环在有向无环图上无非就长这样:

也只能且只会在u处被计算一次

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5 + 10;
 4 vector<int> g[N];
 5 int deg[N], vis[N], n, m, ans;
 6 struct E { int u, v; } e[N * 3];
 7 int main() {
 8     scanf("%d%d", &n, &m);
 9     for(int i = 1 ; i <= m ; ++ i) {
10         scanf("%d%d", &e[i].u, &e[i].v);
11         ++ deg[e[i].u], ++ deg[e[i].v];
12     }
13     for(int i = 1 ; i <= m ; ++ i) {
14         int u = e[i].u, v = e[i].v;
15         if(deg[u] < deg[v] || (deg[u] == deg[v] && u > v)) swap(u, v);
16         g[u].push_back(v);
17     }
18     for(int x = 1 ; x <= n ; ++ x) {
19         for(auto y: g[x]) vis[y] = x;
20         for(auto y: g[x])
21             for(auto z: g[y])
22                 if(vis[z] == x)
23                     ++ ans;
24     }
25     printf("%d\n", ans);
26 }
一个样例程序
复制代码

posted @   KingSann  阅读(2255)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
+

"感谢您的支持,我会继续努力"

微信支付
点击右上角即可分享
微信分享提示