P1989

无向图三元环计数

计算每个点的度数,后将无向边变为有向边,从度数大的点连向度数小的点,如果度数相同则从编号大的点连向编号小的点。

然后我们需要统计的环变成了如下的形式:

三元环

每个三元环当且仅当统计到红点时被统计上。

现在算法步骤如下:

  1. 枚举每个点 $v$。

  2. 对于点 $v$,将其出边所连的点打上临时标记。

  3. 枚举 $v$ 的每个出点的出点,如果该点亦被打上标记,说明找到三元环,直接累积答案即可。

关于这个算法的时间复杂度。

对于每一条边 $x \rightarrow y$,改边被遍历的次数为 $\deg _ { in} (x)$,那么时间复杂度就是 $ O(\sum _ {(x , y) \in E} \deg _ { in } (x)) $。

因为每个连向 $x$ 的点的度数都大于 $\deg _ { in } (x)$,也就是说至少有 $\deg _ {in} (x)$ 个点的点度大于 $\deg _ {in} (x)$,那么就至少存在 $\deg _ { in } (x) ^ 2$ 条边。

而我们又知道必然有 $\deg _ { in } (x) ^ 2 \le m$,所以说 $\deg _ { in } (x) \le \sqrt{m}$。

所以我们最后的复杂度就是 $O(m\sqrt{m})$ 的。

对于四元环计数,我们采取类似的方法,先变成有向图,然后再把四元环分为两类来计数。

不过是多了一些讨论的枚举。

复杂度玄学,只会口胡/qd。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
namespace Ehnaev{
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}
}using Ehnaev::read;using Ehnaev::write;

const ll N=1e5,M=2e5;

ll n,m,tot;
ll deg[N+5],u[M+5],v[M+5],flg[N+5];
ll head[N+5],nxt[M+5],ver[M+5];

inline void Addedge(ll u,ll v) {
ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;
}

int main() {

n=read();m=read();

for(ll i=1;i<=m;i++) {
u[i]=read();v[i]=read();if(u[i]<v[i]) swap(u[i],v[i]);
deg[v[i]]++;deg[u[i]]++;
}

for(ll i=1;i<=m;i++) {
if(deg[v[i]]>deg[u[i]]) Addedge(v[i],u[i]);
else Addedge(u[i],v[i]);
}

ll ans=0;
for(ll v=1;v<=n;v++) {
for(ll i=head[v];i;i=nxt[i]) {flg[ver[i]]=1;}
for(ll i=head[v];i;i=nxt[i]) {
for(ll j=head[ver[i]];j;j=nxt[j]) {if(flg[ver[j]]) ans++;}
}
for(ll i=head[v];i;i=nxt[i]) {flg[ver[i]]=0;}
}

write(ans);

return 0;
}