無向圖三元環計數
無向圖三元環計數
一個科技,在 \(O(m\sqrt m)\) 複雜度內尋找三元環 。
解決的具體問題:給定 \(n\) 點 \(m\) 邊的無向圖,求無序三元點組 \((i,j,k)\) 的數量,滿足圖中存在邊 \((i,j),(j,k),(i,k)\)。
首先,對邊進行重定向。對於兩個點,我們比較它們的度數大小關係,若相等則比較編號大小,從小的點向大的點連邊。這樣可以得到一張 DAG。
為了方便,我們暫時稱 \(v\) 是 \(u\) 的子節點當 \((u,v)\in E\)。
-
在新的 DAG 中每次列舉一個點 \(i\),然後將它所有的出邊連結的點打上標記 \(i\)。
-
再列舉 \(i\)
這樣為什麼是對的呢?
按照演算法的流程,原來圖中的一個三元環 \((i,j,k)\) 在重定向之後一定變成了 \(i\) 向 \(j,k\) 連邊,\(j,k\) 之間再連一條邊的情況。不妨設為 \(j\to k\) 。我們列舉 \(i\) 時會標記 \(j,k\) ,再遍歷 \(j\) 找第三個點的時候一定會找到 \(k\),並且對於這個環,遍歷順序只可能是 \(i,j,k\),也就是我們只能由 \(i\) 來找到這個環。每個點我們只會列舉一次,所以圖中的每一個三元環,我們都會遍歷僅一次。
那麼時間複雜度為什麼 \(O(m\sqrt m)\) 呢?
考慮每條邊 \((u,v)\) 對時間複雜度的貢獻與 \(v\) 的出度同階,總的時間複雜度應當是 \(\sum_{i=1}^m d_v\),這裡 \(d\) 是出度。
-
對於原圖中度數不大於 \(\sqrt{m}\) 的點,新圖中的度數也不可能更大,所以上界 \(\sqrt{m}\)。
-
對於原圖中度數大於 \(\sqrt{m}\) 的點,由於我們只向度數大的點連邊,而度數大於 \(\sqrt{m}\) 的點最多有 \(\sqrt{m}\) 個,度數也不可能超過 \(\sqrt m\)。
綜上,總的時間複雜度 \(O(m\sqrt{m})\)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef long long ll;
const int N=2e5+10;
int head[N],ver[N<<1],nxt[N<<1],tot=0;
void add(int x,int y)
{
ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}
int n,m;
int d[N],vis[N];
PII E[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
d[x]++; d[y]++;
E[i]={x,y};
}
for(int i=1;i<=m;i++)
{
int x=E[i].first,y=E[i].second;
if(d[x]<d[y]) add(x,y);
else if(d[x]==d[y]&&x<y) add(x,y);
else add(y,x);
}
ll ans=0;
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=nxt[j])
vis[ver[j]]=i;
for(int j=head[i];j;j=nxt[j])
{
int y=ver[j];
for(int k=head[y];k;k=nxt[k])
if(vis[ver[k]]==i) ans++;
}
}
printf("%lld",ans);
return 0;
}