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

万恶之源:html

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

1. 有一条链接$i,j$的边ide

2. 有一条链接$j,k$的边spa

3. 有一条链接$k,i$的边3d

举个例子:code

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

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

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

首先要对全部的无向边进行定向,对于任何一条边,从度数大的点连向度数小的点,若是度数相同,从编号小的点连向编号大的点it

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

以后枚举每个点$u$,而后将$u$的全部相邻的点都标记上“被$u$访问了”,而后再枚举$u$的相邻的点$v$,而后再枚举$v$的相邻的点$w$,若是$w$存在“被$u$访问了”的标记,那么$(u,v,w)$就是一个三元环了

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

那么如今就只须要证实三件事:

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

以上图为例,用$deg_u$表示$u$在原图中的度数,则$deg_a \ge deg_b \ge deg_c \ge deg_d \ge deg_e \ge deg_a$

既$deg_a=deg_b=deg_c=deg_d=deg_e=deg_a$

对于度数相同的点,是按照编号从小到大连边的,则$a \le b \le c \le d \le e \le a$

既$a=b=c=d=e$

然而点的编号是$1 \sim n$的全排列,所以不会产生如上的事情,既不存在环

因为这张图有向,并且没有环,所以这张图就是有向无环图

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

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

对于访问$u$,而后访问$v$,而后访问$w$,能够这么考虑:对于每条边($u \rightarrow v$),对时间复杂度的贡献为$out_v$​,其中$out_v$表示$v$的出边个数

这样就转化为了求$\sum_{i=1}^{n}out_i$

不妨将$out_v$分类讨论

1. $out_v \le \sqrt m$​,那么因为$u$链接$v$,所以$deg_u \ge deg_v$​,这样的uu的个数是$O(n)$的,所以这里的时间复杂度为$O(n \sqrt m)$

2. $out_v \gt \sqrt m$,那么因为$u$链接$v$,所以$deg_u \ge deg_v$,既$deg_u \gt \sqrt m$​,这样的$u$的个数是$O(\sqrt m)$的,所以这里的时间复杂度为$O(\sqrt m m)=O(m \sqrt m)$

所以时间复杂度为$O(n+m+n\sqrt m+m\sqrt m)=O(m \sqrt m)$

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 }
一个样例程序
相关文章
相关标签/搜索