【科技】三/四元环计数
【科技】三元环计数
介绍一个小科技,三元环计数,利用复杂度分析证明暴力求解是科学的。
具体问题就是,给定一张 n 个点,m 条边的简单无向图,求解无序三元组 (i,j,k)的数量,其中满足存在边 (i,j),(i,k),(j,k)
分析:
第一秒想到的是直接暴力枚举,但发现一个环上每个点都会把这个环算上一遍,纵使最后容斥除掉
但每个点为基础找三元环的复杂度也是很高的
考虑怎么才能让一个环只被算上一次,这样是最优化的了,再没有别的方法了
正解:
我们先把无向图转成有向图,
给每个点定义一个双关键字(deg,id),其中deg表示度数,id表示标号,
这样对于每一对点都能严格比较出大小。
我们把每一条边重定向成从度数大的点连向度数小的点,我们就可以得到一张有向无环图。
具体怎么找环:
- 枚举一个点i,将所有i点连出的点标记为i。
- 枚举一个点i连出的点j。
- 枚举一个点j连出的点k,如果k的标记是i,那么就找到了一组三元环(i,j,k)。
分析每一个三元环只会在i这个点被算到一次答案。
可以证明这么做的复杂度的一个上界是O(m√m)
因为一个三元环在新图上必定只有一个点的出度为2,然后我们只在这个点上更新三元环数量
code :
#include<bits/stdc++.h>
using namespace std;
const int N = 250005;
int n, m, ans;
int a[N], du[N], vi[N], eu[N], ev[N];
vector<int> g[N];
inline bool cmp(int x, int y) {
return du[x] != du[y]? du[x] > du[y] : x < y;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
for (int i = 1; i <= m; ++i)
scanf("%d%d", &eu[i], &ev[i]), ++du[eu[i]], ++du[ev[i]];
for (int i = 1; i <= m; ++i)
if (cmp(eu[i], ev[i])) g[eu[i]].push_back(ev[i]);
else g[ev[i]].push_back(eu[i]);
for (int i = 1; i <= n; ++i) {
for (int j : g[i]) vi[j] = i;
for (int j : g[i])
for (int k : g[j])
if (vi[k] == i) ++ans;
}
printf("%d\n", ans);
return 0;
}
那四元环怎么办?
类似三元环还是要对边定向。
但此时注意枚举点 u 相邻的点 v 是原图中的边(而非重定向后的边),
而枚举 v相邻的点 w 则要是重定向后的点(可以交换图的顺序),
原因是我们相当于是枚举两个部分拼起来。
还是在 u 被枚举一次,因为 rk[u]<rk[w]
code:
inline bool cmp(const int& _this,const int& _that)
{return d[_this]<d[_that]||(d[_this]==d[_that]&&_this<_that);}
#define con const int&
inline void main() {
n=g(),m=g(); for(R i=1,u,v;i<=m;++i)
u=g(),v=g(),e[u].push_back(v),e[v].push_back(u);
for(R i=1;i<=n;++i) d[id[i]=i]=e[i].size();
sort(id+1,id+n+1,cmp);
for(R i=1;i<=n;++i) rk[id[i]]=i;
for(R u=1;u<=n;++u) for(con v:e[u])
if(rk[v]>rk[u]) f[u].push_back(v);
for(R u=1;u<=n;++u) {
for(con v:e[u]) for(con w:f[v]) if(rk[w]>rk[u]) ans+=c[w],++c[w]; //交换e与f的枚举顺序也是对的。
for(con v:e[u]) for(con w:f[v]) if(rk[w]>rk[u]) c[w]=0; //清空桶。
} printf("%lld\n",ans);
}