1. 程式人生 > 其它 >無向圖三元環計數

無向圖三元環計數

無向圖三元環計數

一個科技,在 \(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\)

    的一個子節點 \(j\),列舉它的所有子節點,若發現有一個子節點 \(k\)\(i\) 的標記,則 存在一個三元環 \(k\)

這樣為什麼是對的呢?

按照演算法的流程,原來圖中的一個三元環 \((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;
}