Skip to content

count_c4.hpp

SECTIONGraph INCLUDEnoya/count_c4.hpp

Count, for every edge of an undirected multigraph, how many four-edge subsets containing it form a simple four-cycle. Parallel edges are first grouped into a weighted simple graph; the degree orientation then charges every length-two path to a low-degree middle vertex.

Verified by counting_c4.

统计简单无向图中的四元环数量;题目要求计算由四个不同顶点组成的环时使用。

Implementation

View on GitHub

#ifndef NOYA_COUNT_C4_HPP
#define NOYA_COUNT_C4_HPP 1

/// @complexity Time: O(n + m sqrt(m)).
/// Space: O(n + m).

#include <algorithm>
#include <cstdint>
#include <numeric>
#include <utility>
#include <vector>

namespace noya {

namespace count_c4_detail {

inline std::vector<std::int64_t>
simple(int vertex_count, std::vector<int> first, std::vector<int> second,
       const std::vector<std::int64_t> &multiplicity) {
  int edge_count = int(multiplicity.size());
  std::vector<int> degree(vertex_count);
  for (int edge = 0; edge < edge_count; edge++) {
    degree[first[edge]]++;
    degree[second[edge]]++;
  }
  std::vector<int> order(vertex_count);
  std::iota(order.begin(), order.end(), 0);
  std::stable_sort(order.begin(), order.end(), [&](int left, int right) {
    return degree[left] < degree[right];
  });
  std::vector<int> rank(vertex_count);
  for (int i = 0; i < vertex_count; i++) {
    rank[order[i]] = i;
  }
  for (int &vertex : first) {
    vertex = rank[vertex];
  }
  for (int &vertex : second) {
    vertex = rank[vertex];
  }
  for (int edge = 0; edge < edge_count; edge++) {
    if (first[edge] < second[edge]) {
      std::swap(first[edge], second[edge]);
    }
  }

  std::vector<int> begin(vertex_count);
  for (int i = 0; i + 1 < vertex_count; i++) {
    begin[i + 1] = begin[i] + degree[order[i]];
  }
  std::vector<int> end = begin;
  std::vector<int> edge_id(edge_count * 2);
  std::vector<int> to(edge_count * 2);
  for (int edge = 0; edge < edge_count; edge++) {
    edge_id[end[first[edge]]] = edge;
    to[end[first[edge]]++] = second[edge];
  }
  std::vector<int> lower_end = end;
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    for (int index = begin[vertex]; index < lower_end[vertex]; index++) {
      int edge = edge_id[index];
      int other = to[index];
      edge_id[end[other]] = edge;
      to[end[other]++] = vertex;
    }
  }

  std::vector<std::int64_t> path_count(vertex_count);
  std::vector<std::int64_t> answer(edge_count);
  for (int vertex = vertex_count - 1; vertex >= 0; vertex--) {
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int first_edge = edge_id[index];
      int middle = to[index];
      end[middle]--;
      for (int next = begin[middle]; next < end[middle]; next++) {
        int second_edge = edge_id[next];
        path_count[to[next]] +=
            multiplicity[first_edge] * multiplicity[second_edge];
      }
    }
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int first_edge = edge_id[index];
      int middle = to[index];
      for (int next = begin[middle]; next < end[middle]; next++) {
        int second_edge = edge_id[next];
        std::int64_t alternatives =
            path_count[to[next]] -
            multiplicity[first_edge] * multiplicity[second_edge];
        answer[first_edge] += alternatives * multiplicity[second_edge];
        answer[second_edge] += alternatives * multiplicity[first_edge];
      }
    }
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int middle = to[index];
      for (int next = begin[middle]; next < end[middle]; next++) {
        path_count[to[next]] = 0;
      }
    }
  }
  return answer;
}

} // namespace count_c4_detail

/// @brief Count, for every edge of an undirected multigraph, how many
/// four-edge subsets containing it form a simple four-cycle. Parallel edges
/// are first grouped into a weighted simple graph; the degree orientation then
/// charges every length-two path to a low-degree middle vertex.
inline std::vector<std::int64_t>
count_c4_per_edge(int vertex_count,
                  const std::vector<std::pair<int, int>> &edges) {
  int edge_count = int(edges.size());
  std::vector<int> order(edge_count);
  std::iota(order.begin(), order.end(), 0);
  std::vector<std::pair<int, int>> normalized = edges;
  for (auto &[first, second] : normalized) {
    if (first > second) {
      std::swap(first, second);
    }
  }
  std::stable_sort(order.begin(), order.end(), [&](int left, int right) {
    return normalized[left] < normalized[right];
  });

  std::vector<int> first, second, group(edge_count);
  std::vector<std::int64_t> multiplicity;
  for (int edge : order) {
    if (first.empty() || first.back() != normalized[edge].first ||
        second.back() != normalized[edge].second) {
      first.push_back(normalized[edge].first);
      second.push_back(normalized[edge].second);
      multiplicity.push_back(0);
    }
    multiplicity.back()++;
    group[edge] = int(first.size()) - 1;
  }
  auto simple_answer = count_c4_detail::simple(
      vertex_count, std::move(first), std::move(second), multiplicity);
  std::vector<std::int64_t> answer(edge_count);
  for (int edge = 0; edge < edge_count; edge++) {
    answer[edge] = simple_answer[group[edge]];
  }
  return answer;
}

} // namespace noya

#endif // NOYA_COUNT_C4_HPP
#include <algorithm>
#include <cstdint>
#include <numeric>
#include <utility>
#include <vector>

/// @complexity Time: O(n + m sqrt(m)).
/// Space: O(n + m).

namespace noya {

namespace count_c4_detail {

inline std::vector<std::int64_t>
simple(int vertex_count, std::vector<int> first, std::vector<int> second,
       const std::vector<std::int64_t> &multiplicity) {
  int edge_count = int(multiplicity.size());
  std::vector<int> degree(vertex_count);
  for (int edge = 0; edge < edge_count; edge++) {
    degree[first[edge]]++;
    degree[second[edge]]++;
  }
  std::vector<int> order(vertex_count);
  std::iota(order.begin(), order.end(), 0);
  std::stable_sort(order.begin(), order.end(), [&](int left, int right) {
    return degree[left] < degree[right];
  });
  std::vector<int> rank(vertex_count);
  for (int i = 0; i < vertex_count; i++) {
    rank[order[i]] = i;
  }
  for (int &vertex : first) {
    vertex = rank[vertex];
  }
  for (int &vertex : second) {
    vertex = rank[vertex];
  }
  for (int edge = 0; edge < edge_count; edge++) {
    if (first[edge] < second[edge]) {
      std::swap(first[edge], second[edge]);
    }
  }

  std::vector<int> begin(vertex_count);
  for (int i = 0; i + 1 < vertex_count; i++) {
    begin[i + 1] = begin[i] + degree[order[i]];
  }
  std::vector<int> end = begin;
  std::vector<int> edge_id(edge_count * 2);
  std::vector<int> to(edge_count * 2);
  for (int edge = 0; edge < edge_count; edge++) {
    edge_id[end[first[edge]]] = edge;
    to[end[first[edge]]++] = second[edge];
  }
  std::vector<int> lower_end = end;
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    for (int index = begin[vertex]; index < lower_end[vertex]; index++) {
      int edge = edge_id[index];
      int other = to[index];
      edge_id[end[other]] = edge;
      to[end[other]++] = vertex;
    }
  }

  std::vector<std::int64_t> path_count(vertex_count);
  std::vector<std::int64_t> answer(edge_count);
  for (int vertex = vertex_count - 1; vertex >= 0; vertex--) {
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int first_edge = edge_id[index];
      int middle = to[index];
      end[middle]--;
      for (int next = begin[middle]; next < end[middle]; next++) {
        int second_edge = edge_id[next];
        path_count[to[next]] +=
            multiplicity[first_edge] * multiplicity[second_edge];
      }
    }
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int first_edge = edge_id[index];
      int middle = to[index];
      for (int next = begin[middle]; next < end[middle]; next++) {
        int second_edge = edge_id[next];
        std::int64_t alternatives =
            path_count[to[next]] -
            multiplicity[first_edge] * multiplicity[second_edge];
        answer[first_edge] += alternatives * multiplicity[second_edge];
        answer[second_edge] += alternatives * multiplicity[first_edge];
      }
    }
    for (int index = begin[vertex]; index < end[vertex]; index++) {
      int middle = to[index];
      for (int next = begin[middle]; next < end[middle]; next++) {
        path_count[to[next]] = 0;
      }
    }
  }
  return answer;
}

} // namespace count_c4_detail

/// @brief Count, for every edge of an undirected multigraph, how many
/// four-edge subsets containing it form a simple four-cycle. Parallel edges
/// are first grouped into a weighted simple graph; the degree orientation then
/// charges every length-two path to a low-degree middle vertex.
inline std::vector<std::int64_t>
count_c4_per_edge(int vertex_count,
                  const std::vector<std::pair<int, int>> &edges) {
  int edge_count = int(edges.size());
  std::vector<int> order(edge_count);
  std::iota(order.begin(), order.end(), 0);
  std::vector<std::pair<int, int>> normalized = edges;
  for (auto &[first, second] : normalized) {
    if (first > second) {
      std::swap(first, second);
    }
  }
  std::stable_sort(order.begin(), order.end(), [&](int left, int right) {
    return normalized[left] < normalized[right];
  });

  std::vector<int> first, second, group(edge_count);
  std::vector<std::int64_t> multiplicity;
  for (int edge : order) {
    if (first.empty() || first.back() != normalized[edge].first ||
        second.back() != normalized[edge].second) {
      first.push_back(normalized[edge].first);
      second.push_back(normalized[edge].second);
      multiplicity.push_back(0);
    }
    multiplicity.back()++;
    group[edge] = int(first.size()) - 1;
  }
  auto simple_answer = count_c4_detail::simple(
      vertex_count, std::move(first), std::move(second), multiplicity);
  std::vector<std::int64_t> answer(edge_count);
  for (int edge = 0; edge < edge_count; edge++) {
    answer[edge] = simple_answer[group[edge]];
  }
  return answer;
}

} // namespace noya