萬惡之源: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 }