无向图三元环计数

无向图三元环计数

一个科技,在 \(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;
}
posted @ 2021-09-03 20:57  RemilaScarlet  阅读(226)  评论(0)    收藏  举报