Cycle Counting Problems

Counting Simple Cycles

Problem 1: Codeforces Beta Round 11 D. A Simple Task

Given a simple graph, count the number of simple cycles in it. A simple cycle is a cycle with no repeated vertices or edges.

The number of vertices satisfies 1≤n≤191\leq n\leq 19.

Solution

Consider a state-compression dynamic programming approach. Let f(s,i)f(s,i) denote the number of paths whose set of visited vertices is ss, whose current vertex is ii, and whose first vertex is the one with the smallest index in the vertex set ss.

For a state f(s,i)f(s,i), enumerate the next vertex uu. If uu belongs to ss and is the one with the smallest index (that is, the starting vertex), add the answer AA by f(s,i)f(s,i). If uu does not belong to ss, add f(s,i)f(s,i) to f(s∪{u},u)f(s\cup\{u\},u).

This also counts 2-cycles (that is, parallel edges), and every non-2-cycle is counted twice because, with the starting vertex fixed, it can be traversed in either direction. Thus the answer is A−m2\dfrac{A-m}2, where mm is the number of edges. The time complexity is O(2nm)O(2^nm).

Example code
#include <iostream>
using namespace std;

int n, m;

struct Edge {
  int to, nxt;
} edge[500];

int cntEdge, head[20];

void addEdge(int u, int v) {
  edge[++cntEdge] = {v, head[u]}, head[u] = cntEdge;
}

long long answer, dp[1 << 19][20];

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;
    addEdge(u, v);
    addEdge(v, u);
  }
  for (int i = 1; i <= n; i++) dp[1 << (i - 1)][i] = 1;
  for (int s = 1; s < (1 << n); s++)
    for (int i = 1; i <= n; i++) {
      if (!dp[s][i]) continue;
      for (int j = head[i]; j; j = edge[j].nxt) {
        int u = i, v = edge[j].to;
        if ((s & -s) > (1 << (v - 1))) continue;
        if (s & (1 << (v - 1))) {
          if ((s & -s) == (1 << (v - 1))) answer += dp[s][u];
        } else
          dp[s | (1 << (v - 1))][v] += dp[s][u];
      }
    }
  cout << (answer - m) / 2 << '\n';
  return 0;
}

Counting Triangles

A triangle in a simple graph GG is an unordered triple (u, v, w)(u,\ v,\ w) such that three edges connect (u, v)(u,\ v), (v, w)(v,\ w), and (w, u)(w,\ u), respectively. The triangle-counting problem asks for the total number of triangles in the graph.

First orient every edge. Direct an edge from the lower-degree vertex to the higher-degree vertex; if the degrees are equal, direct it from the lower-indexed vertex to the higher-indexed vertex. The resulting graph is a directed acyclic graph (DAG).

Proof that the graph has no cycles

Suppose, for a contradiction, that a cycle exists. The degrees of vertices along the cycle would have to increase one by one; for a cycle to close, all vertices would therefore have to have the same degree, but their indices must be different, a contradiction.

Thus the oriented graph certainly has no cycle.

In fact, the orientation rule above defines a partial order, so the graph constructed by this rule (that is, the Hasse diagram of that partial order) must be a DAG.

Enumerate uu and each vertex to which uu points, denoting such a vertex by vv, then, from vv, enumerate a vertex and call it ww, and check whether uu is adjacent to ww.

The time complexity of this algorithm is O(mm)O(m\sqrt m).

Proof of the time complexity

For the orientation step, every edge is traversed, so the time complexity is O(n+m)O(n+m).

For each pair (v, w)(v,\ w), the number of possible uu is at most the indegree of vv, namely d−(v)d^-(v).

If d−(v)≤md^-(v)\leq\sqrt m, then, since the number of ww is at most nn, this part takes O(nm)O(n\sqrt m) time.

If d−(v)>md^-(v) > \sqrt m, then because vv points to ww, we have d(v)≤d(w)d(v) \leq d(w), and hence d(w)>md(w) > \sqrt m. Since the total number of edges is only mm, the number of such ww is at most m\sqrt m, so this part takes O(mm)O(m\sqrt m) time.

The total time complexity is O(n+m+nm+mm)=O(mm)O(n+m+n\sqrt m+m\sqrt m)=O(m\sqrt m).

In fact, the complexity is also correct if edges are oriented from higher-degree vertices to lower-degree vertices; simply exchange the two vertices u, wu,\ w, and the proof above still applies.

Example code (Luogu P1989, counting triangles in an undirected graph)
#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;
}

Problem 2

HDU 6184 Counting Stars

Given an undirected graph with nn vertices and mm edges, count the number of occurrences of the following shape.

Figure: rings count1

2≤n≤1052\leq n\leq 10^5, 1≤m≤min⁡{2×105, n(n−1)2}1\leq m\leq\min\left\{2\times 10^5,\ \dfrac{n(n-1)}2\right\}.

Solution

This shape consists of two triangles sharing an edge. First run the triangle-counting algorithm and count, for each edge, how many triangles contain it. Then enumerate the shared edge. If it belongs to xx triangles, its contribution to the answer is (x2)\dbinom x2.

The time complexity is O(mm)O(m\sqrt m).

Example code
#include <cstring>
#include <iostream>
using namespace std;

int n, m, total;
int deg[200001], u[200001], v[200001];
int edgeId[200001], cnt[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() {
  while (cin >> n >> m) {
    cntEdge = total = 0;
    memset(deg, 0, sizeof deg);
    memset(head, 0, sizeof head);
    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) edgeId[edge[i].to] = i;
      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 (edgeId[w]) cnt[i]++, cnt[j]++, cnt[edgeId[w]]++;
        }
      }
      for (int i = head[u]; i; i = edge[i].nxt) edgeId[edge[i].to] = 0;
    }
    for (int i = 1; i <= m; i++) total += cnt[i] * (cnt[i] - 1) / 2, cnt[i] = 0;
    cout << total << '\n';
  }
  return 0;
}

Counting 4-Cycles

Similarly, a 4-cycle consists of four vertices a, b, c, da,\ b,\ c,\ d such that edges (a, b)(a,\ b), (b, c)(b,\ c), (c, d)(c,\ d), and (d, a)(d,\ a) all exist.

Consider sorting the vertices, with lower-degree vertices first and higher-degree vertices later.

Enumerate the last vertex aa in this order. For every vertex ranked before aa, call it cc; count how many vertices rank before aa, call them bb, and require (a, b)(a,\ b) and (b, c)(b,\ c) to be edges. Any two such vertices bb form a 4-cycle. The number of bb can be found by traversing the neighbors of bb and cc once.

The enumeration has essentially the same complexity as triangle counting, so the time complexity is also O(mm)O(m\sqrt m), assuming n, mn,\ m are of the same order.

Note that (a, b, c, d)(a,\ b,\ c,\ d) and (a, c, b, d)(a,\ c,\ b,\ d) may be two different 4-cycles.

Also, vertices with equal degrees have different ranks, and one must check that a≠ca\neq c.

Example code (LibreOJ P191, counting 4-cycles in an undirected graph)
#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;
}

Problem 3

Gym 102028L Connected Subgraphs

Given an undirected graph with nn vertices and mm edges, count the number of cases in which the induced subgraph of four edges is connected.

4≤n≤1054\leq n\leq 10^5, 4≤m≤2×1054\leq m\leq 2\times 10^5.

Solution

The cases can be divided into five types: a star, a 4-cycle, a triangle with one additional edge incident to a vertex, a four-vertex path with one additional edge incident to an interior vertex, and a five-vertex path.

A star can be counted directly by enumerating vertex degrees and using combinations. A 4-cycle can be counted by the algorithm above. For the triangle case, enumerate a triangle (u, v, w)(u,\ v,\ w); its contribution is [d(u)−2]+[d(v)−2]+[d(w)−2][d(u)-2]+[d(v)-2]+[d(w)-2].

Now consider the fourth type. Enumerate a vertex of degree 22, call it xx, then enumerate one of its adjacent vertices, call it yy, as the vertex of degree 33. Its contribution is [d(x)−1]⋅(d(y)−12)[d(x)-1]\cdot\dbinom{d(y)-1}2. However, neighbors of yy may overlap with neighbors of xx, in which case the graph is actually of the third type. Each overcounted third-type case is counted twice, because it has two degree-33 vertices, so subtract twice the number of third-type cases.

For the last type, first enumerate the middle vertex xx. Its contribution is

∑y∈sonx∑z∈sonx[d(y)−1]⋅[d(z)−1].\sum_{y\in son_x}\sum_{z\in son_x}[d(y)-1]\cdot[d(z)-1].

This also contains overcounted cases. Let yy have a neighbor ss and zz have a neighbor tt. The overcounted cases are:

  1. yy and tt coincide, but ss and zz do not; this is a third-type case;
  2. ss and zz coincide, but yy and tt do not; this is also a third-type case;
  3. both yy and tt, and ss and zz, coincide; this is a triangle;
  4. ss and tt coincide; this is a 4-cycle (the second type).

In the third type, the two degree-22 vertices serving as xx correspond exactly to overcounting cases 1 and 2 above, so subtract twice the number of third-type cases. For a triangle, all its vertices can serve as xx, so it is counted 33 extra times. Similarly, a 4-cycle is counted 44 extra times.

This gives the algorithm for all cases, with time complexity O(n+mm)O(n+m\sqrt m).

Example code
#include <iostream>
#include <vector>
using namespace std;
constexpr int mod = 1000000007;

constexpr long long power(long long a, long long n = mod - 2) {
  long long res = 1;
  while (n) {
    if (n & 1) res = res * a % mod;
    a = a * a % mod;
    n >>= 1;
  }
  return res;
}

constexpr long long power24 = power(24), power2 = power(2);

int n, m;
vector<int> E[100001], E1[100001], E2[100001];
bool vis[100001];
int cnt1[100001], cnt2[100001];

long long solve1();
long long solve2();
long long solve3();
long long solve4();
long long solve5();
long long ans[6];

long long solve1() {
  if (ans[1] != -1) return ans[1];
  ans[1] = 0;
  for (int i = 1; i <= n; i++) {
    int x = E[i].size();
    ans[1] +=
        1ll * x * (x - 1) % mod * (x - 2) % mod * (x - 3) % mod * power24 % mod;
  }
  return ans[1] %= mod;
}

long long solve2() {
  if (ans[2] != -1) return ans[2];
  ans[2] = 0;
  for (int i = 1; i <= n; i++) {
    for (int j : E1[i])
      for (int k : E1[j]) cnt1[k]++;
    for (int j : E2[i])
      for (int k : E1[j])
        if (k != i) cnt2[k]++;
    for (int j : E1[i])
      for (int k : E1[j])
        ans[2] +=
            (1ll * cnt1[k] * (cnt1[k] - 1) / 2 + 1ll * cnt1[k] * cnt2[k]) % mod,
            cnt1[k] = 0;
    for (int j : E2[i])
      for (int k : E1[j])
        if (k != i)
          ans[2] += 1ll * cnt2[k] * (cnt2[k] - 1) / 2 % mod * power2 % mod,
              cnt2[k] = 0;
  }
  return ans[2];
}

long long solve3() {
  if (ans[3] != -1) return ans[3];
  ans[0] = ans[3] = 0;
  for (int i = 1; i <= n; i++) {
    for (int j : E1[i]) vis[j] = true;
    for (int j : E1[i])
      for (int k : E1[j])
        if (vis[k])
          ans[3] =
              (1ll * ans[3] + E[i].size() + E[j].size() + E[k].size() - 6) %
              mod,
          ans[0]++;
    for (int j : E1[i]) vis[j] = false;
  }
  return ans[3];
}

long long solve4() {
  if (ans[4] != -1) return ans[4];
  ans[4] = 0;
  for (int i = 1; i <= n; i++)
    for (int j : E[i])
      (ans[4] += 1ll * (E[j].size() - 1) * (E[j].size() - 2) / 2 *
                 (E[i].size() - 1) % mod) %= mod;
  return ans[4] = (ans[4] - 2 * solve3()) % mod;
}

long long solve5() {
  if (ans[5] != -1) return ans[5];
  ans[5] = 0;
  for (int i = 1; i <= n; i++) {
    long long sum = 0;
    for (int j : E[i]) {
      ans[5] += sum * (E[j].size() - 1) % mod;
      sum += E[j].size() - 1;
    }
  }
  solve3();
  return ans[5] =
             (ans[5] % mod - 2 * solve3() - 4 * solve2() - 3 * ans[0]) % mod;
}

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  int T;
  cin >> T;
  while (T--) {
    ans[5] = ans[1] = ans[2] = ans[4] = ans[3] = -1;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) E[i].clear(), E1[i].clear(), E2[i].clear();
    while (m--) {
      int x, y;
      cin >> x >> y;
      E[x].push_back(y), E[y].push_back(x);
    }
    for (int i = 1; i <= n; i++)
      for (int j : E[i]) {
        if (make_pair(E[i].size(), i) < make_pair(E[j].size(), j))
          E1[i].push_back(j);
        else
          E2[i].push_back(j);
      }
    cout << ((solve5() + solve1() + solve2() + solve4() + solve3()) % mod +
             mod) %
                mod
         << '\n';
  }
  return 0;
}

Exercises

Luogu P3547 [POI2013] CEN-Price List

CodeForces 985G Team Players (inclusion–exclusion)

This content comes from OI Wiki: Cycle Counting Problems. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.