无向图三元环计数

无向图三元环计数

一个科技,在 \(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  阅读(168)  评论(0编辑  收藏  举报