三元环(四元环)计数
模板题:P1989 无向图三元环计数
算法流程:
设
容易发现图变为一个 DAG,有向边
先枚举一个点
时间复杂度
复杂度证明:
设
考虑计算枚举过程中
因为
于是有:
因此复杂度为
提交记录
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+3,M=2e5+3;
basic_string<int>g[N];
int v[N],a[M],b[M],d[N];
int main(){
int n,m,i,j,k,s=0;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i)scanf("%d%d",a+i,b+i),++d[a[i]],++d[b[i]];//统计度数
for(i=1;i<=m;++i)if(d[j=a[i]]<d[k=b[i]]||(d[j]==d[k]&&j<k))g[j]+=k;else g[k]+=j;//给边定向
for(i=1;i<=n;++i){
for(int j:g[i])v[j]=i;//打时间戳
for(int j:g[i])for(int k:g[j])s+=v[k]==i;//统计答案
}
printf("%d",s);
return 0;
}
习题:
CF985G Team Players(容斥)
P3547 [POI2013]CEN-Price List(双向链表)
P4619 [SDOI2018]旧试题(莫比乌斯反演)
四元环计数:
首先和三元环计数一样将边定向。
然后有一个想法:四元环可以用两个
然而这样做是错误的!
为什么三元环可以这样对边定向?因为三元环是无序的,三个点
考虑先对所有点按度数
for(int j:g[i])if(rk[j]>rk[i])for(int k:g[j])if(rk[k]>rk[j])s+=c[k]++;
正确的做法是:
for(int j:g[i])if(rk[i]>rk[j])for(int k:g[j])if(rk[i]>rk[k])s+=c[k]++;
为什么这样做是正确的?确定了
这种做法的时间复杂度同样是
注意四元环的个数上界是
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+3;
basic_string<int>g[N];//此处 g 存的是原图
int rk[N],a[N],d[N],c[N];
int main(){
int n,m,i,j;
long long s=0;
scanf("%d%d",&n,&m);
while(m--)scanf("%d%d",&i,&j),g[i]+=j,g[j]+=i,++d[i],++d[j];
for(i=1;i<=n;++i)a[i]=i;
sort(a+1,a+n+1,[](int x,int y){return d[x]<d[y]||(d[x]==d[y]&&x<y);});
for(i=1;i<=n;++i)rk[a[i]]=i;
for(i=1;i<=n;++i){
for(int j:g[i])if(rk[i]>rk[j])for(int k:g[j])if(rk[i]>rk[k])s+=c[k]++;
for(int j:g[i])if(rk[i]>rk[j])for(int k:g[j])c[k]=0;//清空
}
printf("%lld",s);
return 0;
}
参考资料:
三元环小记(+四元环) command_block
三元环、四元环计数 mrclr