无向图三元环计数
一个科技,在 (O(msqrt 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 o k) 。我们枚举 (i) 时会标记 (j,k) ,再遍历 (j) 找第三个点的时候一定会找到 (k),并且对于这个环,遍历顺序只可能是 (i,j,k),也就是我们只能由 (i) 来找到这个环。每个点我们只会枚举一次,所以图中的每一个三元环,我们都会遍历仅一次。
那么时间复杂度为什么 (O(msqrt m)) 呢?
考虑每条边 ((u,v)) 对时间复杂度的贡献与 (v) 的出度同阶,总的时间复杂度应当是 (sum_{i=1}^m d_v),这里 (d) 是出度。
-
对于原图中度数不大于 (sqrt{m}) 的点,新图中的度数也不可能更大,所以上界 (sqrt{m})。
-
对于原图中度数大于 (sqrt{m}) 的点,由于我们只向度数大的点连边,而度数大于 (sqrt{m}) 的点最多有 (sqrt{m}) 个,度数也不可能超过 (sqrt m)。
综上,总的时间复杂度 (O(msqrt{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;
}