Skip to content

clique_enumeration.hpp

SECTIONGraph INCLUDEnoya/clique_enumeration.hpp

枚举无向图的所有团,并把每个团交给回调统计或计算贡献。

Complexity: Time: O((n + C) ceil(n / 64)) after O(n + m) graph construction, where C is the number of cliques. Space: O(n^2 / 64) g bits and O(n^2 / 64) recursion workspace.

AC 记录:enumerate_cliques

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: O((n + C) ceil(n / 64)) after O(n + m) graph
/// construction, where C is the number of cliques.
/// Space: O(n^2 / 64) g bits and O(n^2 / 64) recursion workspace.

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

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Vertices are ordered by degree and every clique is generated exactly once
/// from its smallest vertex in that order. Recursion intersects the remaining
/// candidates with the chosen vertex's adjacency bitset, so only prefixes that
/// are already cliques are visited.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                         const std::vector<T> &wt) {
  int nw = (n + 63) / 64;
  std::vector<int> deg(n);
  for (auto [a, b] : es) {
    deg[a]++;
    deg[b]++;
  }
  std::vector<int> ord(n);
  for (int i = 0; i < n; i++) {
    ord[i] = i;
  }
  std::stable_sort(ord.begin(), ord.end(),
                   [&](int a, int b) { return deg[a] < deg[b]; });
  std::vector<int> rk(n);
  for (int i = 0; i < n; i++) {
    rk[ord[i]] = i;
  }

  std::vector<std::vector<std::uint64_t>> g(n, std::vector<std::uint64_t>(nw));
  for (auto [a, b] : es) {
    a = rk[a];
    b = rk[b];
    g[a][b >> 6] |= std::uint64_t(1) << (b & 63);
    g[b][a >> 6] |= std::uint64_t(1) << (a & 63);
  }

  T ans{};
  auto dfs = [&](auto &self, std::vector<std::uint64_t> can,
                 const T &prd) -> void {
    for (int wd = 0; wd < nw; wd++) {
      while (can[wd] != 0) {
        int bit = std::countr_zero(can[wd]);
        int u = wd * 64 + bit;
        can[wd] &= can[wd] - 1;
        T np = prd * wt[ord[u]];
        ans += np;
        std::vector<std::uint64_t> nxt(nw);
        for (int i = wd; i < nw; i++) {
          nxt[i] = can[i] & g[u][i];
        }
        self(self, std::move(nxt), np);
      }
    }
  };
  std::vector<std::uint64_t> msk(nw, ~std::uint64_t(0));
  if (n % 64 != 0) {
    msk.back() = (std::uint64_t(1) << (n % 64)) - 1;
  }
  dfs(dfs, std::move(msk), T(1));
  return ans;
}

} // namespace noya
#ifndef NOYA_CLIQUE_ENUMERATION_HPP
#define NOYA_CLIQUE_ENUMERATION_HPP 1

/// @complexity Time: O((n + C) ceil(n / 64)) after O(n + m) graph
/// construction, where C is the number of cliques.
/// Space: O(n^2 / 64) g bits and O(n^2 / 64) recursion workspace.

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

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Vertices are ordered by degree and every clique is generated exactly once
/// from its smallest vertex in that order. Recursion intersects the remaining
/// candidates with the chosen vertex's adjacency bitset, so only prefixes that
/// are already cliques are visited.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                         const std::vector<T> &wt) {
  int nw = (n + 63) / 64;
  std::vector<int> deg(n);
  for (auto [a, b] : es) {
    deg[a]++;
    deg[b]++;
  }
  std::vector<int> ord(n);
  for (int i = 0; i < n; i++) {
    ord[i] = i;
  }
  std::stable_sort(ord.begin(), ord.end(),
                   [&](int a, int b) { return deg[a] < deg[b]; });
  std::vector<int> rk(n);
  for (int i = 0; i < n; i++) {
    rk[ord[i]] = i;
  }

  std::vector<std::vector<std::uint64_t>> g(n, std::vector<std::uint64_t>(nw));
  for (auto [a, b] : es) {
    a = rk[a];
    b = rk[b];
    g[a][b >> 6] |= std::uint64_t(1) << (b & 63);
    g[b][a >> 6] |= std::uint64_t(1) << (a & 63);
  }

  T ans{};
  auto dfs = [&](auto &self, std::vector<std::uint64_t> can,
                 const T &prd) -> void {
    for (int wd = 0; wd < nw; wd++) {
      while (can[wd] != 0) {
        int bit = std::countr_zero(can[wd]);
        int u = wd * 64 + bit;
        can[wd] &= can[wd] - 1;
        T np = prd * wt[ord[u]];
        ans += np;
        std::vector<std::uint64_t> nxt(nw);
        for (int i = wd; i < nw; i++) {
          nxt[i] = can[i] & g[u][i];
        }
        self(self, std::move(nxt), np);
      }
    }
  };
  std::vector<std::uint64_t> msk(nw, ~std::uint64_t(0));
  if (n % 64 != 0) {
    msk.back() = (std::uint64_t(1) << (n % 64)) - 1;
  }
  dfs(dfs, std::move(msk), T(1));
  return ans;
}

} // namespace noya

#endif // NOYA_CLIQUE_ENUMERATION_HPP
#include <algorithm>
#include <bit>
#include <cstdint>
#include <utility>
#include <vector>

/// @complexity Time: O((n + C) ceil(n / 64)) after O(n + m) graph
/// construction, where C is the number of cliques.
/// Space: O(n^2 / 64) g bits and O(n^2 / 64) recursion workspace.

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Vertices are ordered by degree and every clique is generated exactly once
/// from its smallest vertex in that order. Recursion intersects the remaining
/// candidates with the chosen vertex's adjacency bitset, so only prefixes that
/// are already cliques are visited.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                         const std::vector<T> &wt) {
  int nw = (n + 63) / 64;
  std::vector<int> deg(n);
  for (auto [a, b] : es) {
    deg[a]++;
    deg[b]++;
  }
  std::vector<int> ord(n);
  for (int i = 0; i < n; i++) {
    ord[i] = i;
  }
  std::stable_sort(ord.begin(), ord.end(),
                   [&](int a, int b) { return deg[a] < deg[b]; });
  std::vector<int> rk(n);
  for (int i = 0; i < n; i++) {
    rk[ord[i]] = i;
  }

  std::vector<std::vector<std::uint64_t>> g(n, std::vector<std::uint64_t>(nw));
  for (auto [a, b] : es) {
    a = rk[a];
    b = rk[b];
    g[a][b >> 6] |= std::uint64_t(1) << (b & 63);
    g[b][a >> 6] |= std::uint64_t(1) << (a & 63);
  }

  T ans{};
  auto dfs = [&](auto &self, std::vector<std::uint64_t> can,
                 const T &prd) -> void {
    for (int wd = 0; wd < nw; wd++) {
      while (can[wd] != 0) {
        int bit = std::countr_zero(can[wd]);
        int u = wd * 64 + bit;
        can[wd] &= can[wd] - 1;
        T np = prd * wt[ord[u]];
        ans += np;
        std::vector<std::uint64_t> nxt(nw);
        for (int i = wd; i < nw; i++) {
          nxt[i] = can[i] & g[u][i];
        }
        self(self, std::move(nxt), np);
      }
    }
  };
  std::vector<std::uint64_t> msk(nw, ~std::uint64_t(0));
  if (n % 64 != 0) {
    msk.back() = (std::uint64_t(1) << (n % 64)) - 1;
  }
  dfs(dfs, std::move(msk), T(1));
  return ans;
}

} // namespace noya