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


萬惡之源:

給定一張無重邊、無自環的無向圖(點數為$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. 定向后的圖是一個有向無環圖

以上圖為例,用$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 }
一個樣例程序


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM