Skip to content

clique_enumeration.hpp

SECTIONGraph INCLUDEnoya/graph/clique_enumeration.hpp

计算所有非空团的顶点权乘积之和;保留度数排序下的乘法与累加顺序,使用局部位集和复用缓冲。自环与重边不产生额外团。

Complexity: Time: \(O(n \log (n + 1) + m + M D + C (1 + \lceil D / 64\rceil))\), with \(M\) distinct non-loop edges, \(D\) maximum forward degree, \(C\) nonempty cliques. Space: \(O(n + m + D \lceil D / 64\rceil)\), including \(O(D)\) recursion.

AC 记录:enumerate_cliques。

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(n log(n+1) + m + M D + C (1+ceil(D/64))), with M
/// distinct non-loop edges, D maximum forward degree, C nonempty cliques.
/// Space: O(n + m + D ceil(D/64)), including O(D) recursion.

#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <span>
#include <utility>
#include <vector>

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Require n >= 0, valid endpoints and at least n weights. Loops/duplicates
/// do not create extra cliques. Keep the original order: increasing raw
/// degree (loops counted twice), then vertex ID, with depth-first prefix order.
/// T needs T{}, T(1), copying, * and +=; products are left-associated.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                        const std::vector<T> &wt) {
  assert(n >= 0 && wt.size() >= std::size_t(n));
  using mask = std::uint64_t;
  std::vector<std::size_t> deg(n);
  for (auto [a, b] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    ++deg[a];
    ++deg[b];
  }
  T ans{};
  if (es.empty()) {
    for (int u = 0; u < n; ++u) ans += T(1) * wt[u];
    return ans;
  }
  std::vector<int> ord(n), rank(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]; });
  for (int i = 0; i < n; ++i) rank[ord[i]] = i;

  // Store only edges pointing forward in the fixed multiplication order.
  std::vector<std::size_t> start(std::size_t(n) + 1);
  for (auto [a, b] : es)
    if (a != b) ++start[std::min(rank[a], rank[b]) + 1];
  for (int u = 0; u < n; ++u) start[u + 1] += start[u];
  std::vector<int> adj(start.back());
  {
    auto at = start;
    for (auto [a, b] : es) {
      a = rank[a];
      b = rank[b];
      if (a > b) std::swap(a, b);
      if (a != b) adj[at[a]++] = b;
    }
  }
  std::vector<int> id(n, -1);
  std::size_t end = 0;
  for (int u = 0; u < n; ++u) {
    auto begin = start[u], stop = start[u + 1];
    start[u] = end;
    for (auto i = begin; i < stop; ++i) {
      int v = adj[i];
      if (id[v] != u) {
        id[v] = u;
        adj[end++] = v;
      }
    }
    std::sort(adj.begin() + start[u], adj.begin() + end);
  }
  start[n] = end;
  adj.resize(end);
  std::fill(id.begin(), id.end(), -1);
  auto neighbors = [&](int u) {
    return std::span<const int>(adj).subspan(start[u], start[u + 1] - start[u]);
  };

  std::vector<mask> local, buf;
  for (int root = 0; root < n; ++root) {
    auto vs = neighbors(root);
    int d = int(vs.size()), words = d / 64 + (d % 64 != 0);
    T base = T(1) * wt[ord[root]];
    ans += base;
    if (!d) continue;
    for (int i = 0; i < d; ++i) id[vs[i]] = i;
    // A large independent neighborhood needs no quadratic local matrix.
    bool ready = d <= 64;
    if (ready) local.assign(d, 0);
    for (int i = 0; i < d; ++i) {
      for (int v : neighbors(vs[i])) {
        int j = id[v];
        if (j < 0) continue;
        if (!ready) {
          local.assign(std::size_t(d) * words, 0);
          ready = true;
        }
        local[std::size_t(i) * words + j / 64] |= mask(1) << (j % 64);
      }
    }
    for (int v : vs) id[v] = -1;
    if (!ready) {
      for (int v : vs) ans += base * wt[ord[v]];
      continue;
    }
    if (d <= 64) {
      auto dfs = [&](auto &self, mask can, const T &prod) -> void {
        while (can) {
          int u = std::countr_zero(can);
          can &= can - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask nxt = can & local[u];
          if (nxt) self(self, nxt, next);
        }
      };
      dfs(dfs, d == 64 ? ~mask(0) : (mask(1) << d) - 1, base);
      continue;
    }
    // One reusable row per depth; do not allocate for each visited clique.
    buf.resize(std::size_t(d + 1) * words);
    std::fill_n(buf.data(), words, ~mask(0));
    if (d % 64) buf[words - 1] = (mask(1) << (d % 64)) - 1;
    auto dfs = [&](auto &self, int depth, int first, const T &prod) -> void {
      mask *can = buf.data() + std::size_t(depth) * words;
      for (int w = first; w < words; ++w) {
        while (can[w]) {
          int u = w * 64 + std::countr_zero(can[w]);
          can[w] &= can[w] - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask *nxt = can + words;
          bool any = false;
          for (int j = w; j < words; ++j) {
            nxt[j] = can[j] & local[std::size_t(u) * words + j];
            any |= nxt[j] != 0;
          }
          // Earlier words are already consumed and may contain stale data.
          if (any) self(self, depth + 1, w, next);
        }
      }
    };
    dfs(dfs, 0, 0, base);
  }
  return ans;
}

} // namespace noya

 // NOYA_CLIQUE_ENUMERATION_HPP
#ifndef NOYA_CLIQUE_ENUMERATION_HPP
#define NOYA_CLIQUE_ENUMERATION_HPP 1

/// @complexity Time: O(n log(n+1) + m + M D + C (1+ceil(D/64))), with M
/// distinct non-loop edges, D maximum forward degree, C nonempty cliques.
/// Space: O(n + m + D ceil(D/64)), including O(D) recursion.

#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <span>
#include <utility>
#include <vector>

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Require n >= 0, valid endpoints and at least n weights. Loops/duplicates
/// do not create extra cliques. Keep the original order: increasing raw
/// degree (loops counted twice), then vertex ID, with depth-first prefix order.
/// T needs T{}, T(1), copying, * and +=; products are left-associated.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                        const std::vector<T> &wt) {
  assert(n >= 0 && wt.size() >= std::size_t(n));
  using mask = std::uint64_t;
  std::vector<std::size_t> deg(n);
  for (auto [a, b] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    ++deg[a];
    ++deg[b];
  }
  T ans{};
  if (es.empty()) {
    for (int u = 0; u < n; ++u) ans += T(1) * wt[u];
    return ans;
  }
  std::vector<int> ord(n), rank(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]; });
  for (int i = 0; i < n; ++i) rank[ord[i]] = i;

  // Store only edges pointing forward in the fixed multiplication order.
  std::vector<std::size_t> start(std::size_t(n) + 1);
  for (auto [a, b] : es)
    if (a != b) ++start[std::min(rank[a], rank[b]) + 1];
  for (int u = 0; u < n; ++u) start[u + 1] += start[u];
  std::vector<int> adj(start.back());
  {
    auto at = start;
    for (auto [a, b] : es) {
      a = rank[a];
      b = rank[b];
      if (a > b) std::swap(a, b);
      if (a != b) adj[at[a]++] = b;
    }
  }
  std::vector<int> id(n, -1);
  std::size_t end = 0;
  for (int u = 0; u < n; ++u) {
    auto begin = start[u], stop = start[u + 1];
    start[u] = end;
    for (auto i = begin; i < stop; ++i) {
      int v = adj[i];
      if (id[v] != u) {
        id[v] = u;
        adj[end++] = v;
      }
    }
    std::sort(adj.begin() + start[u], adj.begin() + end);
  }
  start[n] = end;
  adj.resize(end);
  std::fill(id.begin(), id.end(), -1);
  auto neighbors = [&](int u) {
    return std::span<const int>(adj).subspan(start[u], start[u + 1] - start[u]);
  };

  std::vector<mask> local, buf;
  for (int root = 0; root < n; ++root) {
    auto vs = neighbors(root);
    int d = int(vs.size()), words = d / 64 + (d % 64 != 0);
    T base = T(1) * wt[ord[root]];
    ans += base;
    if (!d) continue;
    for (int i = 0; i < d; ++i) id[vs[i]] = i;
    // A large independent neighborhood needs no quadratic local matrix.
    bool ready = d <= 64;
    if (ready) local.assign(d, 0);
    for (int i = 0; i < d; ++i) {
      for (int v : neighbors(vs[i])) {
        int j = id[v];
        if (j < 0) continue;
        if (!ready) {
          local.assign(std::size_t(d) * words, 0);
          ready = true;
        }
        local[std::size_t(i) * words + j / 64] |= mask(1) << (j % 64);
      }
    }
    for (int v : vs) id[v] = -1;
    if (!ready) {
      for (int v : vs) ans += base * wt[ord[v]];
      continue;
    }
    if (d <= 64) {
      auto dfs = [&](auto &self, mask can, const T &prod) -> void {
        while (can) {
          int u = std::countr_zero(can);
          can &= can - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask nxt = can & local[u];
          if (nxt) self(self, nxt, next);
        }
      };
      dfs(dfs, d == 64 ? ~mask(0) : (mask(1) << d) - 1, base);
      continue;
    }
    // One reusable row per depth; do not allocate for each visited clique.
    buf.resize(std::size_t(d + 1) * words);
    std::fill_n(buf.data(), words, ~mask(0));
    if (d % 64) buf[words - 1] = (mask(1) << (d % 64)) - 1;
    auto dfs = [&](auto &self, int depth, int first, const T &prod) -> void {
      mask *can = buf.data() + std::size_t(depth) * words;
      for (int w = first; w < words; ++w) {
        while (can[w]) {
          int u = w * 64 + std::countr_zero(can[w]);
          can[w] &= can[w] - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask *nxt = can + words;
          bool any = false;
          for (int j = w; j < words; ++j) {
            nxt[j] = can[j] & local[std::size_t(u) * words + j];
            any |= nxt[j] != 0;
          }
          // Earlier words are already consumed and may contain stale data.
          if (any) self(self, depth + 1, w, next);
        }
      }
    };
    dfs(dfs, 0, 0, base);
  }
  return ans;
}

} // namespace noya

#endif // NOYA_CLIQUE_ENUMERATION_HPP
/// @complexity Time: O(n log(n+1) + m + M D + C (1+ceil(D/64))), with M
/// distinct non-loop edges, D maximum forward degree, C nonempty cliques.
/// Space: O(n + m + D ceil(D/64)), including O(D) recursion.

#include <algorithm>
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <span>
#include <utility>
#include <vector>

namespace noya {

/// @brief Sum the products of vertex weights over all nonempty cliques.
/// Require n >= 0, valid endpoints and at least n weights. Loops/duplicates
/// do not create extra cliques. Keep the original order: increasing raw
/// degree (loops counted twice), then vertex ID, with depth-first prefix order.
/// T needs T{}, T(1), copying, * and +=; products are left-associated.
template <class T>
T sum_of_clique_products(int n, const std::vector<std::pair<int, int>> &es,
                        const std::vector<T> &wt) {
  assert(n >= 0 && wt.size() >= std::size_t(n));
  using mask = std::uint64_t;
  std::vector<std::size_t> deg(n);
  for (auto [a, b] : es) {
    assert(0 <= a && a < n && 0 <= b && b < n);
    ++deg[a];
    ++deg[b];
  }
  T ans{};
  if (es.empty()) {
    for (int u = 0; u < n; ++u) ans += T(1) * wt[u];
    return ans;
  }
  std::vector<int> ord(n), rank(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]; });
  for (int i = 0; i < n; ++i) rank[ord[i]] = i;

  // Store only edges pointing forward in the fixed multiplication order.
  std::vector<std::size_t> start(std::size_t(n) + 1);
  for (auto [a, b] : es)
    if (a != b) ++start[std::min(rank[a], rank[b]) + 1];
  for (int u = 0; u < n; ++u) start[u + 1] += start[u];
  std::vector<int> adj(start.back());
  {
    auto at = start;
    for (auto [a, b] : es) {
      a = rank[a];
      b = rank[b];
      if (a > b) std::swap(a, b);
      if (a != b) adj[at[a]++] = b;
    }
  }
  std::vector<int> id(n, -1);
  std::size_t end = 0;
  for (int u = 0; u < n; ++u) {
    auto begin = start[u], stop = start[u + 1];
    start[u] = end;
    for (auto i = begin; i < stop; ++i) {
      int v = adj[i];
      if (id[v] != u) {
        id[v] = u;
        adj[end++] = v;
      }
    }
    std::sort(adj.begin() + start[u], adj.begin() + end);
  }
  start[n] = end;
  adj.resize(end);
  std::fill(id.begin(), id.end(), -1);
  auto neighbors = [&](int u) {
    return std::span<const int>(adj).subspan(start[u], start[u + 1] - start[u]);
  };

  std::vector<mask> local, buf;
  for (int root = 0; root < n; ++root) {
    auto vs = neighbors(root);
    int d = int(vs.size()), words = d / 64 + (d % 64 != 0);
    T base = T(1) * wt[ord[root]];
    ans += base;
    if (!d) continue;
    for (int i = 0; i < d; ++i) id[vs[i]] = i;
    // A large independent neighborhood needs no quadratic local matrix.
    bool ready = d <= 64;
    if (ready) local.assign(d, 0);
    for (int i = 0; i < d; ++i) {
      for (int v : neighbors(vs[i])) {
        int j = id[v];
        if (j < 0) continue;
        if (!ready) {
          local.assign(std::size_t(d) * words, 0);
          ready = true;
        }
        local[std::size_t(i) * words + j / 64] |= mask(1) << (j % 64);
      }
    }
    for (int v : vs) id[v] = -1;
    if (!ready) {
      for (int v : vs) ans += base * wt[ord[v]];
      continue;
    }
    if (d <= 64) {
      auto dfs = [&](auto &self, mask can, const T &prod) -> void {
        while (can) {
          int u = std::countr_zero(can);
          can &= can - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask nxt = can & local[u];
          if (nxt) self(self, nxt, next);
        }
      };
      dfs(dfs, d == 64 ? ~mask(0) : (mask(1) << d) - 1, base);
      continue;
    }
    // One reusable row per depth; do not allocate for each visited clique.
    buf.resize(std::size_t(d + 1) * words);
    std::fill_n(buf.data(), words, ~mask(0));
    if (d % 64) buf[words - 1] = (mask(1) << (d % 64)) - 1;
    auto dfs = [&](auto &self, int depth, int first, const T &prod) -> void {
      mask *can = buf.data() + std::size_t(depth) * words;
      for (int w = first; w < words; ++w) {
        while (can[w]) {
          int u = w * 64 + std::countr_zero(can[w]);
          can[w] &= can[w] - 1;
          T next = prod * wt[ord[vs[u]]];
          ans += next;
          mask *nxt = can + words;
          bool any = false;
          for (int j = w; j < words; ++j) {
            nxt[j] = can[j] & local[std::size_t(u) * words + j];
            any |= nxt[j] != 0;
          }
          // Earlier words are already consumed and may contain stale data.
          if (any) self(self, depth + 1, w, next);
        }
      }
    };
    dfs(dfs, 0, 0, base);
  }
  return ans;
}

} // namespace noya

 // NOYA_CLIQUE_ENUMERATION_HPP