Skip to content

stirling_small_prime.hpp

SECTIONMath INCLUDEnoya/stirling_small_prime.hpp

在较小素数模下查询下标可达 \(10^{18}\) 的第一类或第二类 Stirling 数。

\[ \displaystyle x^n=\sum_{k=0}^n S(n,k)x^{\underline{k}} \]

Complexity: Time: O(p^2) preprocessing and O(log_p n) per query. Space: O(p^2) for each requested Stirling kind.

AC 记录:stirling_number_of_the_first_kind_small_p_large_n, stirling_number_of_the_second_kind_small_p_large_n

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(p^2) preprocessing and O(log_p n) per query.
/// Space: O(p^2) for each requested Stirling kind.

#include <cassert>
#include <cstdint>
#include <vector>

namespace noya {

/// @brief Query signed first-kind and second-kind Stirling numbers modulo a
/// small fixed prime for indices as large as 10^18. Over F_p, the identities
/// (x)_p=x^p-x and x^p=x split a large falling factorial or power into p-sized
/// blocks. Repeating the split leaves one small Stirling number with indices
/// below p and one binomial coefficient. Lucas' theorem evaluates the latter
/// digit by digit. The constructor can omit either Stirling table when only one
/// kind is needed.
class stirling_small_prime_table {
public:
  explicit stirling_small_prime_table(std::uint32_t p, bool b1 = true,
                                      bool b2 = true)
      : p_(p) {
    assert(p >= 2);
    build_binomial();
    if (b1) {
      build_first();
    }
    if (b2) {
      build_second();
    }
  }

  std::uint32_t first_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!a_.empty());
    if (k > n) {
      return 0;
    }
    std::uint64_t bs = n / p_;
    int rem = int(n % p_);
    if (bs > k) {
      return 0;
    }
    std::uint64_t quo = (k - bs) / (p_ - 1);
    int sk = int((k - bs) % (p_ - 1));
    if (sk == 0 && rem > 0) {
      if (quo == 0) {
        return 0;
      }
      sk = int(p_ - 1);
      quo--;
    }
    if (bs < quo || sk > rem) {
      return 0;
    }
    std::uint32_t res = multiply(lucas_choose(bs, quo), a_[rem][sk]);
    if (((bs + quo) & 1) != 0 && res != 0) {
      res = p_ - res;
    }
    return res;
  }

  std::uint32_t second_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!b_.empty());
    if (k > n) {
      return 0;
    }
    if (n == 0) {
      return k == 0 ? 1 : 0;
    }
    std::uint64_t bs = k / p_;
    int sk = int(k % p_);
    if (n < bs) {
      return 0;
    }
    std::uint64_t quo = (n - bs) / (p_ - 1);
    int rem = int((n - bs) % (p_ - 1));
    if (rem == 0) {
      if (quo == 0) {
        return 0;
      }
      rem = int(p_ - 1);
      quo--;
    }
    if (sk > rem) {
      return 0;
    }
    if (rem < int(p_ - 1)) {
      return multiply(lucas_choose(quo, bs), b_[rem][sk]);
    }
    if (sk == 0) {
      return bs == 0 ? 0 : lucas_choose(quo, bs - 1);
    }
    return multiply(lucas_choose(quo, bs), b_[p_ - 1][sk]);
  }

private:
  std::uint32_t p_;
  std::vector<std::vector<std::uint32_t>> bin;
  std::vector<std::vector<std::uint32_t>> a_;
  std::vector<std::vector<std::uint32_t>> b_;

  std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
    return std::uint32_t(a * b % p_);
  }

  std::uint32_t lucas_choose(std::uint64_t n, std::uint64_t k) const {
    if (k > n) {
      return 0;
    }
    std::uint32_t res = 1;
    while (n > 0 || k > 0) {
      int hi = int(n % p_);
      int lo = int(k % p_);
      if (lo > hi) {
        return 0;
      }
      res = multiply(res, bin[hi][lo]);
      n /= p_;
      k /= p_;
    }
    return res;
  }

  void build_binomial() {
    bin.resize(p_);
    bin[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      bin[n] = bin[n - 1];
      bin[n].push_back(0);
      for (int k = 1; k <= n; k++) {
        bin[n][k] += bin[n - 1][k - 1];
        if (bin[n][k] >= p_) {
          bin[n][k] -= p_;
        }
      }
    }
  }

  void build_first() {
    a_.resize(p_);
    a_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      a_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          a_[n][k] += a_[n - 1][k - 1];
        }
        if (k < n) {
          a_[n][k] += multiply(a_[n - 1][k], p_ - n + 1);
        }
        if (a_[n][k] >= p_) {
          a_[n][k] -= p_;
        }
      }
    }
  }

  void build_second() {
    b_.resize(p_);
    b_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      b_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          b_[n][k] += b_[n - 1][k - 1];
        }
        if (k < n) {
          b_[n][k] += multiply(b_[n - 1][k], k);
        }
        if (b_[n][k] >= p_) {
          b_[n][k] -= p_;
        }
      }
    }
  }
};

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

/// @complexity Time: O(p^2) preprocessing and O(log_p n) per query.
/// Space: O(p^2) for each requested Stirling kind.

#include <cassert>
#include <cstdint>
#include <vector>

namespace noya {

/// @brief Query signed first-kind and second-kind Stirling numbers modulo a
/// small fixed prime for indices as large as 10^18. Over F_p, the identities
/// (x)_p=x^p-x and x^p=x split a large falling factorial or power into p-sized
/// blocks. Repeating the split leaves one small Stirling number with indices
/// below p and one binomial coefficient. Lucas' theorem evaluates the latter
/// digit by digit. The constructor can omit either Stirling table when only one
/// kind is needed.
class stirling_small_prime_table {
public:
  explicit stirling_small_prime_table(std::uint32_t p, bool b1 = true,
                                      bool b2 = true)
      : p_(p) {
    assert(p >= 2);
    build_binomial();
    if (b1) {
      build_first();
    }
    if (b2) {
      build_second();
    }
  }

  std::uint32_t first_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!a_.empty());
    if (k > n) {
      return 0;
    }
    std::uint64_t bs = n / p_;
    int rem = int(n % p_);
    if (bs > k) {
      return 0;
    }
    std::uint64_t quo = (k - bs) / (p_ - 1);
    int sk = int((k - bs) % (p_ - 1));
    if (sk == 0 && rem > 0) {
      if (quo == 0) {
        return 0;
      }
      sk = int(p_ - 1);
      quo--;
    }
    if (bs < quo || sk > rem) {
      return 0;
    }
    std::uint32_t res = multiply(lucas_choose(bs, quo), a_[rem][sk]);
    if (((bs + quo) & 1) != 0 && res != 0) {
      res = p_ - res;
    }
    return res;
  }

  std::uint32_t second_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!b_.empty());
    if (k > n) {
      return 0;
    }
    if (n == 0) {
      return k == 0 ? 1 : 0;
    }
    std::uint64_t bs = k / p_;
    int sk = int(k % p_);
    if (n < bs) {
      return 0;
    }
    std::uint64_t quo = (n - bs) / (p_ - 1);
    int rem = int((n - bs) % (p_ - 1));
    if (rem == 0) {
      if (quo == 0) {
        return 0;
      }
      rem = int(p_ - 1);
      quo--;
    }
    if (sk > rem) {
      return 0;
    }
    if (rem < int(p_ - 1)) {
      return multiply(lucas_choose(quo, bs), b_[rem][sk]);
    }
    if (sk == 0) {
      return bs == 0 ? 0 : lucas_choose(quo, bs - 1);
    }
    return multiply(lucas_choose(quo, bs), b_[p_ - 1][sk]);
  }

private:
  std::uint32_t p_;
  std::vector<std::vector<std::uint32_t>> bin;
  std::vector<std::vector<std::uint32_t>> a_;
  std::vector<std::vector<std::uint32_t>> b_;

  std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
    return std::uint32_t(a * b % p_);
  }

  std::uint32_t lucas_choose(std::uint64_t n, std::uint64_t k) const {
    if (k > n) {
      return 0;
    }
    std::uint32_t res = 1;
    while (n > 0 || k > 0) {
      int hi = int(n % p_);
      int lo = int(k % p_);
      if (lo > hi) {
        return 0;
      }
      res = multiply(res, bin[hi][lo]);
      n /= p_;
      k /= p_;
    }
    return res;
  }

  void build_binomial() {
    bin.resize(p_);
    bin[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      bin[n] = bin[n - 1];
      bin[n].push_back(0);
      for (int k = 1; k <= n; k++) {
        bin[n][k] += bin[n - 1][k - 1];
        if (bin[n][k] >= p_) {
          bin[n][k] -= p_;
        }
      }
    }
  }

  void build_first() {
    a_.resize(p_);
    a_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      a_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          a_[n][k] += a_[n - 1][k - 1];
        }
        if (k < n) {
          a_[n][k] += multiply(a_[n - 1][k], p_ - n + 1);
        }
        if (a_[n][k] >= p_) {
          a_[n][k] -= p_;
        }
      }
    }
  }

  void build_second() {
    b_.resize(p_);
    b_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      b_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          b_[n][k] += b_[n - 1][k - 1];
        }
        if (k < n) {
          b_[n][k] += multiply(b_[n - 1][k], k);
        }
        if (b_[n][k] >= p_) {
          b_[n][k] -= p_;
        }
      }
    }
  }
};

} // namespace noya

#endif // NOYA_STIRLING_SMALL_PRIME_HPP
#include <cassert>
#include <cstdint>
#include <vector>

/// @complexity Time: O(p^2) preprocessing and O(log_p n) per query.
/// Space: O(p^2) for each requested Stirling kind.

namespace noya {

/// @brief Query signed first-kind and second-kind Stirling numbers modulo a
/// small fixed prime for indices as large as 10^18. Over F_p, the identities
/// (x)_p=x^p-x and x^p=x split a large falling factorial or power into p-sized
/// blocks. Repeating the split leaves one small Stirling number with indices
/// below p and one binomial coefficient. Lucas' theorem evaluates the latter
/// digit by digit. The constructor can omit either Stirling table when only one
/// kind is needed.
class stirling_small_prime_table {
public:
  explicit stirling_small_prime_table(std::uint32_t p, bool b1 = true,
                                      bool b2 = true)
      : p_(p) {
    assert(p >= 2);
    build_binomial();
    if (b1) {
      build_first();
    }
    if (b2) {
      build_second();
    }
  }

  std::uint32_t first_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!a_.empty());
    if (k > n) {
      return 0;
    }
    std::uint64_t bs = n / p_;
    int rem = int(n % p_);
    if (bs > k) {
      return 0;
    }
    std::uint64_t quo = (k - bs) / (p_ - 1);
    int sk = int((k - bs) % (p_ - 1));
    if (sk == 0 && rem > 0) {
      if (quo == 0) {
        return 0;
      }
      sk = int(p_ - 1);
      quo--;
    }
    if (bs < quo || sk > rem) {
      return 0;
    }
    std::uint32_t res = multiply(lucas_choose(bs, quo), a_[rem][sk]);
    if (((bs + quo) & 1) != 0 && res != 0) {
      res = p_ - res;
    }
    return res;
  }

  std::uint32_t second_kind(std::uint64_t n, std::uint64_t k) const {
    assert(!b_.empty());
    if (k > n) {
      return 0;
    }
    if (n == 0) {
      return k == 0 ? 1 : 0;
    }
    std::uint64_t bs = k / p_;
    int sk = int(k % p_);
    if (n < bs) {
      return 0;
    }
    std::uint64_t quo = (n - bs) / (p_ - 1);
    int rem = int((n - bs) % (p_ - 1));
    if (rem == 0) {
      if (quo == 0) {
        return 0;
      }
      rem = int(p_ - 1);
      quo--;
    }
    if (sk > rem) {
      return 0;
    }
    if (rem < int(p_ - 1)) {
      return multiply(lucas_choose(quo, bs), b_[rem][sk]);
    }
    if (sk == 0) {
      return bs == 0 ? 0 : lucas_choose(quo, bs - 1);
    }
    return multiply(lucas_choose(quo, bs), b_[p_ - 1][sk]);
  }

private:
  std::uint32_t p_;
  std::vector<std::vector<std::uint32_t>> bin;
  std::vector<std::vector<std::uint32_t>> a_;
  std::vector<std::vector<std::uint32_t>> b_;

  std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
    return std::uint32_t(a * b % p_);
  }

  std::uint32_t lucas_choose(std::uint64_t n, std::uint64_t k) const {
    if (k > n) {
      return 0;
    }
    std::uint32_t res = 1;
    while (n > 0 || k > 0) {
      int hi = int(n % p_);
      int lo = int(k % p_);
      if (lo > hi) {
        return 0;
      }
      res = multiply(res, bin[hi][lo]);
      n /= p_;
      k /= p_;
    }
    return res;
  }

  void build_binomial() {
    bin.resize(p_);
    bin[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      bin[n] = bin[n - 1];
      bin[n].push_back(0);
      for (int k = 1; k <= n; k++) {
        bin[n][k] += bin[n - 1][k - 1];
        if (bin[n][k] >= p_) {
          bin[n][k] -= p_;
        }
      }
    }
  }

  void build_first() {
    a_.resize(p_);
    a_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      a_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          a_[n][k] += a_[n - 1][k - 1];
        }
        if (k < n) {
          a_[n][k] += multiply(a_[n - 1][k], p_ - n + 1);
        }
        if (a_[n][k] >= p_) {
          a_[n][k] -= p_;
        }
      }
    }
  }

  void build_second() {
    b_.resize(p_);
    b_[0] = {1};
    for (int n = 1; n < int(p_); n++) {
      b_[n].assign(n + 1, 0);
      for (int k = 0; k <= n; k++) {
        if (k > 0) {
          b_[n][k] += b_[n - 1][k - 1];
        }
        if (k < n) {
          b_[n][k] += multiply(b_[n - 1][k], k);
        }
        if (b_[n][k] >= p_) {
          b_[n][k] -= p_;
        }
      }
    }
  }
};

} // namespace noya