普通环计数

给定一个简单图,求图中简单环的数目。简单环是指没有重复顶点或边的环。

结点数目

三元环计数

三元环 指的是一个简单图 中的一个无序三元组 满足存在三条边分别连接 。而 三元环计数问题 要求计算出图中所有三元环的数量。

首先给所有边定向。我们规定从度数小的点指向度数大的点,度数相同就从编号小的点指向编号大的点。那么此时此图是一张有向无环图(DAG)。

枚举 指向的点 ,再在 指向的点中枚举 ,检验 是否与 相连即可。

这个算法的时间复杂度为

时间复杂度证明

对于定向部分,遍历了所有的边,时间复杂度

对于每一对 的数量都不超过 的入度

,由于 的个数至多为 ,所以这部分时间复杂度为

,由于 指向 ,所以 ,得出 ,但是总边数只有 ,所以这样的 的个数至多为 ,故时间复杂度为

总时间复杂度为

事实上,如果定向时从度数大的点指向度数小的点,复杂度也正确,只需要交换 两个点,上述证明也成立。

#include <iostream>
using namespace std;
 
int n, m, total;
int deg[200001], u[200001], v[200001];
bool vis[200001];
 
struct Edge {
  int to, nxt;
} edge[200001];
 
int cntEdge, head[100001];
 
void addEdge(int u, int v) {
  edge[++cntEdge] = {v, head[u]}, head[u] = cntEdge;
}
 
int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> m;
  for (int i = 1; i <= m; i++) cin >> u[i] >> v[i], deg[u[i]]++, deg[v[i]]++;
  for (int i = 1; i <= m; i++) {
    if ((deg[u[i]] == deg[v[i]] && u[i] > v[i]) || deg[u[i]] < deg[v[i]])
      swap(u[i], v[i]);
    addEdge(u[i], v[i]);
  }
  for (int u = 1; u <= n; u++) {
    for (int i = head[u]; i; i = edge[i].nxt) vis[edge[i].to] = true;
    for (int i = head[u]; i; i = edge[i].nxt) {
      int v = edge[i].to;
      for (int j = head[v]; j; j = edge[j].nxt) {
        int w = edge[j].to;
        if (vis[w]) total++;
      }
    }
    for (int i = head[u]; i; i = edge[i].nxt) vis[edge[i].to] = false;
  }
  cout << total << '\n';
  return 0;
}

例题 2

给定一张有 个点和 条边的无向图,求下面图形的出现次数。

四元环计数

类似地,四元环 就是指四个点 满足 均有边连接。

考虑先对点进行排序。度数小的排在前面,度数大的排在后面。

考虑枚举排在最后面的点 ,此时只需要对于每个比 排名更前的点 ,都求出有多少个排名比 前的点 满足 有边。然后只需要从这些 中任取两个都能成为一个四元环。求 的数量只需要遍历一遍 即可。

注意到我们枚举的复杂度本质上与枚举三元环等价,所以时间复杂度也是 (假设 同阶)。

值得注意的是, 可以是两个不同的四元环。

另外,度数相同的结点的排名将不相同,并且需要注意判断

#include <iostream>
#include <vector>
using namespace std;
 
int n, m, deg[100001], cnt[100001];
vector<int> E[100001], E1[100001];
 
long long total;
 
int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    E[u].push_back(v);
    E[v].push_back(u);
    deg[u]++, deg[v]++;
  }
  for (int u = 1; u <= n; u++)
    for (int v : E[u])
      if (deg[u] > deg[v] || (deg[u] == deg[v] && u > v)) E1[u].push_back(v);
  for (int a = 1; a <= n; a++) {
    for (int b : E1[a])
      for (int c : E[b]) {
        if (deg[a] < deg[c] || (deg[a] == deg[c] && a <= c)) continue;
        total += cnt[c]++;
      }
    for (int b : E1[a])
      for (int c : E[b]) cnt[c] = 0;
  }
  cout << total << '\n';
  return 0;
}

例题 3

给定一张有 个点和 条边的无向图,求四条边的导出子图连通的情况数。

习题

洛谷 P3547 [POI2013] CEN-Price List

CodeForces 985G Team Players(容斥原理)

此文件夹下有0条笔记。